백엔드 웹 개발 노트6.4 - Spring MVC HTTP RequestBody
HTTP Request Body
HTTP Request Message - 단순 텍스트
요청 파라미터와 다르게 HTTP 바디를 통해서 데이터가 직접 데이터로 넘어오는 경우는 @RequestParam, _@ModelAttribute_를 사용할 수 없다.
가장 원초적인 방법으로 텍스트를 읽는법을 알아보자.
1. InputStream을 사용한 방법.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyController {
@PostMapping("kms-request-body-mappingV1")
public void requestbodyV1(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");
}
}
결과
불편한점이 있다. 클래스들의 이름이 너무 길다. 설정도 해줘야하고.. Spring에서는 이러한 설정들을 본인들이 직접하여 개발자가 편리하게 messagebody를 다룰 수 있도록 도와준다.
2. InputStream, Writer를 이용한 방법.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyController {
@PostMapping("/kms-request-body-mappingV2")
public void requestbodyV2(InputStream inputStream, Writer writer) throws IOException {
String messagebody = StreamUtils.copyToString(inputStream, StandardCharsets.UTF_8);
log.info("v2 messagebody = {}",messagebody);
writer.write("ok");
}
}
스프링 MVC는 다음 파라미터를 지원한다.
- InputStream(Reader): HTTP 요청 메시지 바디의 내용을 직접 조회
- OutputStream(Writer): HTTP 응답 메시지의 바디에 직접 결과 출력
v1에서는 request에서 직접 꺼내서 썼다면, v2에서는 아예 꺼낸 Stream을 인자로 받아서 쓰고 있다는 점이다. 더 줄일 수 있을까? 있다.!!
3. HttpEntity<> 이용한 방법.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyController {
@PostMapping("/kms-request-body-mappingV3")
public HttpEntity<String> requestbodyV3(HttpEntity<String> httpEntity) {
String messagebody = httpEntity.getBody();
log.info("v3 messagebody = {}",messagebody);
return new HttpEntity<>("im kms OK");
}
}
스프링 MVC가 지원하는 기능이다.
- HTTPEntity<>는 HTTP Header,body를 좀 더 편히 조회할 수 있게 도와준다.
- HTTPEntity<>는 응답용으로도 사용될 수 있다.
HTTPEntity를 상속받은 객체들이 있는데, 이는 더 개발자가 요구하는 바를 더 명확하게 밝히고 기능이 강화되어있다.
4. HttpEntity<>를 상속받은 RequestEntity, ResponseEntity객체
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyController {
@PostMapping("/kms-request-body-mappingV4")
public HttpEntity<String> requestbodyV4(RequestEntity<String> requestEntity){
String messagebody = requestEntity.getBody();
log.info("v4 messagebody = {}",messagebody);
return new ResponseEntity<String>("ok", HttpStatus.CREATED);
}
}
이마저도 코드가 길어서 복잡하자. 더 줄여보자.
5. @ResponseBody, @RequestBody 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
package hello.springmvc.basic.request;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyController {
@ResponseBody
@PostMapping("/kms-request-body-mappingV5")
public String requestV5(@RequestBody String messagebody){
log.info("v5 messagebody = {}", messagebody);
return "v5 ok!!";
}
}
코드가 굉장히 간결해졌다.
이게 요즘 방식이다.
- @RequestBody
@RequestBody 를 사용하면 HTTP 메시지 바디 정보를 편리하게 조회할 수 있다.
참고로 헤더 정보가 필요하다면 HttpEntity 를 사용하거나 @RequestHeader 를 사용하면 된다.
이렇게 메시지 바디를 직접 조회하는 기능은 요청 파라미터를 조회하는 @RequestParam , @ModelAttribute 와는 전혀 관계가 없다.
요청 파라미터 vs HTTP 메시지 바디
요청 파라미터를 조회하는 기능: @RequestParam , @ModelAttribute
HTTP 메시지 바디를 직접 조회하는 기능: @RequestBody@ResponseBody
@ResponseBody 를 사용하면 응답 결과를 HTTP 메시지 바디에 직접 담아서 전달할 수 있다. 물론 이 경우에도 view를 사용하지 않는다.
HTTP Request Message - JSON
JSON 형태를 조회하는 방법을 알아볼 것이다.
먼저 원시적인 방법을 보겠다.
1. Servlet방식으로 JSON 조회
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package hello.springmvc.basic.request;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.BmiData;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.mapper.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyJSONController {
private ObjectMapper mapper = new ObjectMapper();
@PostMapping("/kms-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("message = {}",messagebody);
BmiData data = mapper.readValue(messagebody,BmiData.class);
log.info("name = {}, height = {}, weight = {}",data.getName(),data.getHeight(),data.getWeight());
response.getWriter().write("ok");
}
}
2. ResponseBody, RequestBody 사용.
전통적인 방식이다. mapper를 통해 데이터를 넣고 그 데이터를 출력한다.
줄이자.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package hello.springmvc.basic.request;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.BmiData;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.mapper.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyJSONController {
private ObjectMapper mapper = new ObjectMapper();
@ResponseBody
@PostMapping("/kms-request-body-json-v2")
public String requestBodyJsonV2(@RequestBody String messagebody) throws JsonProcessingException {
log.info("message = {}", messagebody);
BmiData data = mapper.readValue(messagebody, BmiData.class);
log.info("name = {}, height = {}, weight = {}",data.getName(),data.getHeight(),data.getWeight());
return "json v2 ok";
}
}
리턴값을 String으로 바꾸고 메소드 앞에 @ResponseBody 를달아줬다.
또한, 파라미터에는 @RequestBody 를 달아줘서 body값을 직접 가져오도록 하였다.
근데 이것도 불편하다. mapper에 너무 의존적이다. mapper를 안 쓸순 없을까? 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package hello.springmvc.basic.request;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.BmiData;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.mapper.Mapper;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyJSONController {
@ResponseBody
@PostMapping("/kms-request-body-json-v3")
public String requestBodyJsonV3(@RequestBody BmiData data) {
log.info("name = {}, height = {}, weight = {}",data.getName(),data.getHeight(),data.getWeight());
return "json v3 ok";
}
}
이처럼 @RequestBody 안에 객체를 넣어버리는 것이다. 단, 요청으로 들어오는 JSON Key의 이름과 객체의 필드 이름이 같아야 Spring의 HTTP 메시지컨버터가 이를 찾고 매핑해준다.
참고로 @RequestBody 를 생략해버리면 스프링의 규칙에 따라 @ModelAttribute 가 적용된다. 즉 생략하면 HTTP 메시지 바디가 아니라 요청 파라미터로 처리해버린다.
요청을 일단 받았지만 모두 NULL의 형태가 들어감.
3. HTTPEntity로 받기.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
package hello.springmvc.basic.request;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.BmiData;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.mapper.Mapper;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyJSONController {
@ResponseBody
@PostMapping("/kms-request-body-json-v4")
public String requestBodyJsonV4(HttpEntity<BmiData> data) {
BmiData body = data.getBody();
log.info("name = {}, height = {}, weight = {}",body.getName(),body.getHeight(),body.getWeight());
return "json v4 ok";
}
}
만약 JSON형태로 그대로 반환하고 싶으면 어떻게 할까?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
package hello.springmvc.basic.request;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import hello.springmvc.basic.BmiData;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.mapper.Mapper;
import org.springframework.http.HttpEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
@Slf4j
@Controller
public class RequestBodyJSONController {
@ResponseBody
@PostMapping("/kms-request-body-json-v5")
public BmiData requestBodyJsonV5(@RequestBody BmiData data) {
log.info("name = {}, height = {}, weight = {}",data.getName(),data.getHeight(),data.getWeight());
return data;
}
}
이렇게 리턴값에 클래스명과 리턴값으로 그냥 넘겨버리면 된다.
- @RequestBody 요청
JSON 요청 -> HTTP 메시지 컨버터 -> 객체 - @ResponseBody 응답
객체 -> HTTP 메시지 컨버터 -> JSON 응답
즉, HTTP 메시지 컨버터가 중간에서 원하는 데이터의 형태로 바꿔주는 역할을 한다.
요청을 보낼때는 Content-Type이 application/json 이어야하고, 응답을 보낼때에는 Accept가 application/json이어야 한다.!!!