[Spring Web MVC] 요청 정보 다루기
@GetMapping("/mapping/{userId}")
public String mappingPath(@PathVariable("userId") String id){
log.info("logging... {}", id);
return "chk";
}
URL 경로에 변수를 넣어 템플릿 형식으로 설정할 수 있고, @PathVariable 애너테이션으로 그 값을 꺼내 메서드에서 사용할 수 있다. (경로 변수)
@GetMapping("/mapping/users/{userId}/order/{orderID}")
public String mappingPath(@PathVariable("userId") String id, @PathVariable("orderID") String order){
log.info("logging... {} {}", id, order);
return "chk";
}
다중 매핑도 지원한다.
@GetMapping(value ="/mapping/", params = "mode=debug")
public String mappingTest(){
log.info("logging..");
return "chk";
}
해당 URL 요청이 들어왔을 때 특정 파라미터가 있어야 요청이 수락되도록 할 수 있다.
(사실 경로 변수 방식을 더 많이 사용한다.)
params 대신 headers를 사용하면 특정 헤더가 있어야지만 요청이 수락된다.
params 대신 consumes를 사용하면 해당 요청의 content-type이 consumes로 등록한 값과 일치할 때 요청이 수락된다.
사실 headers를 사용해서 값을 조금 조작해도 되는데, consumes로 등록 시 내부적으로 좀 더 처리하는 작업이 있다.
params 대신 produces를 사용하면 해당 요청의 accept 값이 produces로 등록한 값과 일치할 때 요청이 수락된다.
회원 관리 API를 설계해보자.
회원 목록 조회: GET /users
회원 등록: POST /users
회원 조회: GET /users/{userId}
회원 수정: PATCH /users/{userId}
회원 삭제: DELETE /users/{userId}
@RestController
@RequestMapping("/mapping/users")
public class MappingClassController {
@GetMapping
public String user(){
return "get users";
}
@PostMapping
public String addUser(){
return "post user";
}
@GetMapping("/{userId}")
public String findUser(@PathVariable String userId){
return "get userId=" + userId;
}
@PatchMapping("/{userId}")
public String updateUser(@PathVariable String userId){
return "update userId=" + userId;
}
@DeleteMapping("/{userId}")
public String deleteUser(@PathVariable String userId){
return "delete userId=" + userId;
}
}
URL 매핑만 처리한다고 하면 이렇게 작성하면 된다.
@Slf4j
@RestController
public class RequestHeaderController {
@RequestMapping("/headers")
public String headers(HttpServletRequest request,
HttpServletResponse response,
HttpMethod httpMethod,
Locale locale,
@RequestHeader MultiValueMap<String, String> headerMap,
@RequestHeader("host") String host,
@CookieValue(value = "myCookie", required = true) String cookie){
log.info("request={}", request);
log.info("response={}", response);
log.info("httpMethod={}", httpMethod);
log.info("locale={}", locale);
log.info("headerMap={}", headerMap);
log.info("header host={}", host);
log.info("myCookie={}", cookie);
return "ok";
}
}
애너테이션 기반 스프링 컨트롤러는 매개변수로 정말 다양한 값을 받아서 사용할 수 있다.
HTTP 헤더 관련 정보를 조회하는 방법을 살펴보자.
Request와 Response는 이미 많이 봤고..
HttpMethod : 요청 메서드를 조회한다.
Locale : Locale 정보를 조회한다. (지역)
@RequestHeader MultiValueMap : 모든 HTTP 헤더를 MultiValueMap 형식으로 조회한다.
여기서 MultiValueMap은 Map 자료구조와 유사한데, 하나의 키에 여러 가지 값을 받을 수 있는 자료구조이다.
같은 헤더에 같은 값이 들어가는 경우 사용한다. (키로 조회 시 리스트가 반환된다.)
@RequestHeader("host") : 특정 HTTP 헤더를 조회한다.
@CookieValue(...) : 특정 쿠키를 조회한다.
이 외에도 다양한 파라미터를 받을 수 있으니, 스프링 공식 문서를 참고하자.
@Controller
@Slf4j
public class RequestParamController {
@RequestMapping("/request-param-v1")
public void requestParam1(HttpServletRequest request, HttpServletResponse response)throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
log.info("{}, {}", username, age);
PrintWriter writer = response.getWriter();
writer.write("check");
}
}
클라이언트에서 서버로 데이터를 전송할 때는 쿼리 파라미터 / HTML Form / HTTP 메세지 바디 방식을 사용했었는데, 이 중에서 쿼리 파라미터와 HTML Form을 통해 전송하는 경우는 getParameter 메서드를 통해 서버에서 요청 데이터를 가져올 수 있었다.
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam("username") String username,
@RequestParam("age") int age,){
log.info("{} {}", username, age);
return "check";
}
@RequestParam 애너테이션을 통해 getParameter() 메서드를 사용하지 않고도 데이터를 읽을 수 있다.
여기서 @ResponseBody 애너테이션은 반환값을 HTTP 응답 본문(body)으로 보낼 때 사용한다.
@ResponseBody
@RequestMapping("/request-param-v2")
public String requestParamV2(
@RequestParam String username,
@RequestParam int age){
log.info("{} {}", username, age);
return "da";
}
지금처럼 파라미터 이름과 변수명이 같으면 파라미터 이름을 생략해서 사용할 수 있다.
@ResponseBody
@RequestMapping("/request-param-v4")
public String requestParamV4(
String username,
int age){
log.info("{} {}", username, age);
return "dasafd";
}
파라미터의 이름과 변수명이 같으면 @RequestParam 애너테이션도 생략할 수 있다.
이 때 타입은 String int 등 단순 타입이어야 한다.
그런데 너무 생략해도 코드를 이해하기 쉽지 않을 수 있으니 적당히 사용하자.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username,
@RequestParam(required = false) Integer age){
log.info("{} {}", username, age);
return "dassdfaafd";
}
@RequestParam 애너테이션에 required 옵션을 추가해서 요청 메세지에 해당 요소가 들어올 경우만 요청을 수락하도록 할 수 있다. (기본값 true)
여기서 int 대신 Integer를 사용했는데, false로 설정 시 null 이 들어가야 하지만 Primitive 타입에는 null을 넣을 수 없어 null을 사용할 수 있는 래퍼 클래스로 설정해야 한다.
@ResponseBody
@RequestMapping("/request-param-required")
public String requestParamRequired(
@RequestParam(required = true) String username,
@RequestParam(required = false, defaultValue = "231") int age){
log.info("{} {}", username, age);
return "dassdfaafd";
}
defaultValue를 지정해 값이 들어오지 않을 경우 기본값을 설정할 수 있다.
이렇게 하면 int를 그대로 사용할 수 있다.
@ResponseBody
@RequestMapping("/request-param-map")
public String requestParamMap(@RequestParam Map<String, Object> paramMap){
log.info("{} {}", paramMap.get("username"), paramMap.get("age"));
return "dafd";
}
파라미터로 Map 또는 MultiValueMap을 주면 모든 파라미터를 조회할 수 있다.
요청 파라미터를 받아서 응답에 필요한 객체를 만들고 그 객체에 값을 넣어줘야 한다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttribute(
@RequestParam String username,
@RequestParam int age){
HelloData helloData = new HelloData();
helloData.setAge(32);
helloData.setUsername("asdf");
return "32";
}
이런 식으로.. 요청 파라미터에서의 데이터를 저장할 객체를 만들어야 했다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttribute(@ModelAttribute HelloData helloData){
return "32";
}
@ModelAttribute 애너테이션을 사용하면 모델 객체를 알아서 생성하고 요청 파라미터의 값도 알아서 채워준다.
1. 객체 생성
2. 요청 파라미터의 이름으로 해당 객체의 프로퍼티(setter, getter)를 찾아 값을 바인딩한다.
요청 시 타입이 어긋나면 BindException을 뱉는다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttribute(HelloData helloData){
return "32";
}
사실 @ModelAttribute 애너테이션은 생략할 수 있다.
그런데 @RequestParam도 생략할 수 있지 않았나? 좀 헷갈릴 것 같은데...
생략 시 규칙이 적용된다.
String int 등 단순 타입은 @RequestParam을 사용하고, 나머지 타입은 @ModelAttribute를 사용한다.
나머지 타입 중 argument resolver로 지정해 둔 타입은 @ModelAttribute 가 적용되지 않는데..
@RequestParam 은 요청 파라미터를 받는 작업을 수행하고, 바인딩은 스스로 해야 한다.
@ModelAttribute 는 요청 파라미터를 받아서 객체 생성부터 바인딩, 모델에 담기까지의 작업을 모두 처리한다.
이 정도로 이해하자.
메세지 바디를 통해 직접 데이터가 넘어오는 경우는 @RequestParam 과 @ModelAttribute 애너테이션을 사용할 수 없다.
@PostMapping("/request-body-string-v1")
public void requestBodyString(HttpServletRequest request, HttpServletResponse response) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messagebody {}", messageBody);
}
가장 단순한 방법으로 InputStream 으로 메세지 바디의 데이터를 직접 읽어올 수 있다.
@PostMapping("/request-body-string-v2")
public void requestBodyStringV2(InputStream inputStream, Writer responseWriter) throws IOException {
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messagebody {}", messageBody);
responseWriter.write("check");
}
파라미터 값으로 InputStream을 바로 받아올 수 있다. (Writer도 마찬가지)
String 은 바이트 코드이기 때문에 Charset을 지정해 줘야 한다. (미지정 시 OS에서 기본값으로 설정된 Charset으로 설정됨)
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) throws IOException {
String body = httpEntity.getBody();
log.info("messagebody {}", body);
return new HttpEntity<>("check");
}
더 간소화 할 수 있다.
HttpEntity는 HTTP 헤더, 바디 등 여러 정보를 편하게 조회할 수 있도록 해 준다.
메세지 바디 정보를 바로 조회할 수 있고, 이전처럼 요청 파라미터를 조회하는 기능과는 전혀 연관관계가 없다.
리턴 타입으로 HttpEntity<String>을 사용해 응답에도 HttpEntity를 사용할 수 있다.
메세지 바디 정보를 직접 반환하고, 뷰를 조회하지 않는다.
HttpEntity를 상속받은 RequestEntity, ResponseEntity를 사용해 요청과 응답에서 사용할 수도 있다.
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) throws IOException {
log.info("messagebody {}", messageBody);
return "ok123";
}
여기서 더 간단하게 바꿀 수 있다.
파라미터로 @RequestBody 애너테이션을 사용해 메세지 바디의 내용을 바로 가져올 수 있고, 메서드에 @ResponseBody 애너테이션을 붙여 뷰를 찾지 않고 응답 결과를 메세지 바디에 담아 그대로 반환하도록 할 수 있다.
이 방법을 가장 많이 사용한다.
그런데 메세지 바디에 텍스트만 적어서 보내지는 않는다.
주로 사용하는 데이터 형식인 JSON 형식을 다뤄보자.
기본적으로 ObjectMapper를 사용한다.
request 와 response를 통해 읽는 경우 / @RequestBody 애너테이션으로 읽고 HelloData 객체로 변환해서 읽는 경우는 앞의 예시와 동일하니 넘어가자.
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
@RequestBody에 객체를 파라미터로 넘긴다.
HTTP 메세지 컨버터는 HTTP 메세지 바디의 내용을 원하는 문자나 객체로 변환해준다.
즉, 원하는 타입이 JSON 타입이면 (content-type 이 application/JSON 이면)
HelloData helloData = objectMapper.readValue(messageBody, HelloData.class);
위의 코드를 HTTP 메세지 컨버터가 실행해준다.
여기서는 @RequestBody 애너테이션을 생략할 수 없다.
앞에서 살펴본 바와 같이 객체 타입은 @ModelAttribute가 요청 파라미터를 처리하기 때문에 생략 시 HTTP 메세지 바디가 아니라 요청 파라미터를 처리하게 된다.
@ResponseBody
@PostMapping("/request-body-json-v4")
public HelloData requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return data;
}
물론 앞에서와 같이 HttpEntity를 사용해도 된다.
JSON 타입으로 요청이 들어오면 HTTP 메세지 컨버터가 해당 타입을 객체로 변환하고,
응답할 때는 객체를 다시 JSON타입으로 변경 후 응답한다.
'Spring > Spring Web MVC' 카테고리의 다른 글
[Spring Web MVC] Spring MVC와 Thymeleaf 예시 (0) | 2022.08.18 |
---|---|
[Spring Web MVC] 응답 정보 다루기 (0) | 2022.08.17 |
[Spring Web MVC] Spring MVC (0) | 2022.08.16 |
[Spring Web MVC] Adaptor (0) | 2022.08.16 |
[Spring Web MVC] Model (0) | 2022.08.15 |
댓글
이 글 공유하기
다른 글
-
[Spring Web MVC] Spring MVC와 Thymeleaf 예시
[Spring Web MVC] Spring MVC와 Thymeleaf 예시
2022.08.18 -
[Spring Web MVC] 응답 정보 다루기
[Spring Web MVC] 응답 정보 다루기
2022.08.17 -
[Spring Web MVC] Spring MVC
[Spring Web MVC] Spring MVC
2022.08.16 -
[Spring Web MVC] Adaptor
[Spring Web MVC] Adaptor
2022.08.16