클라이언트에서 서버로 요청 데이터를 전달할 때 가장 많이 사용하는 3가지 방식에 대해서 설명하고자한다.
/url?username=hello&age=20
와 같이 HTTP 메시지 body없이 쿼리 파라미터에 데이터를 보내는 방식이다.
html
에 form 태그에 작성된 내용을 전송 받는 방법이다.
HTTP API에서 주로 사용하는 방식이다.
이 두가지 방식을 합친 이유는 클라이언트에서 보내는 방식이 다르게 보일지 몰라도 HTTP 메시지에는 같은 형식으로 저장되기 때문에 구분 없이 사용되기때문에 같이 설명하려고한다.
/url?username=hello&age=20
을 처리해야한다는 상황을 가정하고 컨트롤러를 작성해보자.
Servlet을 사용할 때 처럼 HttpServletRequest
통해서 값을 입력 받는 방식이다.
@RequestMapping("/request-param-v1")
public void requestParamV1(
HttpServletRequest request,HttpServletResponse response
) throws IOException {
String username = request.getParameter("username");
int age = Integer.parseInt(request.getParameter("age"));
}
@RequestParam
라는 어노테이션을 이용하는 방식으로, ()안에 쿼리 파라미터의 키값을 넣어주면 뒤에있는 데이터 형식으로 변환해서 변수에 넣어준다.
1번의 방법보다 훨씬 코드가 깔끔해지는 것을 볼 수 있다.
값을 받을 변수명과 쿼리 파라미터의 키 값이 같다면 ()안에
쿼리 파라미터의 키값
을 넣는 것을 생략할 수 있다.
@RequestParam("username") String memberName
->@RequestParam String memberName
또 이때 변수의 형식이 단순 데이터(int, String, Integer 등 ) 이라면@RequestParam
까지 생략할 수 있다.
@RequestParam("username") String memberName
->String memberName
(이렇게 되면 spring 내부에서required=false
를 적용한다.)
하지만 어노테이션까지 생략하는 것은 명시적이지 않아 권하지 않는다.
@RequestParam 추가 속성
- required : 파라미터 필수 여부 (기본 값 true)
- defaultValue : 기본 값 세팅 (기본 값 없음)
@RequestMapping("/request-param-v2")
public void requestParamV2(
@RequestParam("username") String memberName,
@RequestParam("age") int memberAge) {
log.info("username={}, age={}", memberName, memberAge);
}
만약 값을 객체로 받고싶다면 @ModelAttribute
를 사용해보자.
이 어노테이션을 사용한다면 객체도 자동으로 생성되고, 생성된 객체에 값도 자동으로 넣어준다.
이 어노테이션은 객체의
setter()
를 통해서 값을 넣기 때문에 꼭setter()
가 정의되어있어야한다.
이 어노테이션도 생략할 수는 있지만 권장하지 않는다.
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData) {
log.info("username={}, age={}", helloData.getUsername(), helloData.getAge());
return "ok";
}
HTTP 메시지 body를 통해 데이터가 넘어오는 경우에는 위의 방식을 사용할 수 없다.
{"username":"hello", "age":20}
content-type: application/json
이러한 형식의 데이터를 받아서 처리한다고 가정하고 컨트롤러를 작성해보자
HttpServletRequest
를 사용해서 직접 HTTP 메시지 바디에서 데이터를 InputStream
을 이용해 직접 읽을 수 있다.
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
response.getWriter().write("ok");
}
private ObjectMapper objectMapper = new ObjectMapper();
@PostMapping("/request-body-json-v1")
public void requestBodyJsonV1(
HttpServletRequest request,
HttpServletResponse response
) throws IOException {
ServletInputStream inputStream = request.getInputStream();
String messageBody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("messageBody={}", messageBody);
HelloData data = objectMapper.readValue(messageBody, HelloData.class);
log.info("username={}, age={}", data.getUsername(), data.getAge());
response.getWriter().write("ok");
}
이 InputStream
은 위의 HttpServletResponse
에서 InputStream을 생성하던 것을 spring이 대신 해줘서 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("ok");
}
HttpEntity
란 HTTP 메시지를 편하게 조회할 수 있게 해준다.
HttpEntity
를 상속받은 RequestEntity
와 ResponseEntity
도 같은 기능을 제공한다.
@PostMapping("/request-body-string-v3")
public HttpEntity<String> requestBodyStringV3(HttpEntity<String> httpEntity) {
String messageBody = httpEntity.getBody();
log.info("messageBody={}", messageBody);
return new HttpEntity<>("ok");
}
@RequestBody
어노테이션은 HTTP 메시지 바디 정보를 편리하게 조회할 수 있게 해준다.
@ResponseBody
@PostMapping("/request-body-string-v4")
public String requestBodyStringV4(@RequestBody String messageBody) {
log.info("messageBody={}", messageBody);
return "ok";
}
@RequestBody
데이터 타입이 객체라면 @ModelAttribute
와 같이 객체가 생성되며 자동으로 값이 들어간다.@RequestParam나 @ModelAttribute`와 같이 생략은 불가능하다
@ResponseBody
@PostMapping("/request-body-json-v3")
public String requestBodyJsonV3(@RequestBody HelloData data) {
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
물론 HttpEntity
에서도 동일하게 동작된다.
@ResponseBody
@PostMapping("/request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<HelloData> httpEntity) {
HelloData data = httpEntity.getBody();
log.info("username={}, age={}", data.getUsername(), data.getAge());
return "ok";
}
HTTP 요청시에
content-type
이application/json
인지 꼭! 확인해야 한다. 그래야 JSON을 처리할 수 있는 HTTP 메시지 컨버터가 실행된다.