프로그래밍/Spring Framework

RestTemplate POST 요청 시 유의할 점. (RequestBody HashMap 사용)

모지사바하 2015. 8. 10. 15:40

스프링에서 제공하는 RestTemplate 을 이용하여 Post 요청을 하려면,


RestTemplate template = new RestTemplate();
return template.postForObject("http://111.222.333.444/api/test/", map, Map.class);

위와 같은 형태로 하면 된다. 


위의 경우는 Same Policy Origin 때문에 CORS 를 해야하는 상황에서 Proxy 를 둔 상황이다.


아무튼,


위와 같이 Post 요청을 할 때 requestBody 를 인자로 전달해야하는데 이 requestBody 가 postForObject의 두번째 인자이고, Object 타입이다. 위 예에선 두번째 인자로 보낸 map이다.


requestBody 를 map으로 간편히 보내고자 할 때, HashMap을 이용하면 절대 안된다. 실제로 HashMap으로 요청하면 아무런 값도 전달되지 않는다.


구글링 해보면 금방 나오지만, requestBody 는 HashMap 을 넘기면 안되고 MultiValueMap 을 넘겨서 처리해야한다 .


왜 그럴까?


내부 소스를 들여다보면 금방 답을 알 수 있다.


RestTemplate의 doExecute 메소드를 보자..


try {
ClientHttpRequest request = createRequest(url, method);
if (requestCallback != null) {
requestCallback.doWithRequest(request);
}
response = request.execute();
handleResponse(url, method, response);
if (responseExtractor != null) {
return responseExtractor.extractData(response);
}
else {
return null;
}
}

response = request.execute(); 부분이 최종적으로 Rest Api 를 호출하는 부분이다.




이 때, requestBody 를 넘겼다면,


if (requestCallback != null) {
requestCallback.doWithRequest(request);
}

부분이 실행되는데, requestCallback.doWithRequest(request)  내부를 보면,  


for (HttpMessageConverter<?> messageConverter : getMessageConverters()) {
if (messageConverter.canWrite(requestType, requestContentType)) {
if (!requestHeaders.isEmpty()) {
httpRequest.getHeaders().putAll(requestHeaders);
}
if (logger.isDebugEnabled()) {
if (requestContentType != null) {
logger.debug("Writing [" + requestBody + "] as \"" + requestContentType +
"\" using [" + messageConverter + "]");
}
else {
logger.debug("Writing [" + requestBody + "] using [" + messageConverter + "]");
}

}
((HttpMessageConverter<Object>) messageConverter).write(
requestBody, requestContentType, httpRequest);
return;
}
}

위와 같은 일을 한다.


스프링의 RequestMappingHandlerAdaper 에서 디폴트로 제공하는 메시지 컨버터와 프로젝트에 설정된 메시지컨버터를 loop 하면서 해당 메시지컨버터가 requestBody 의 Type 을 지원하는지를 채크하고, requestBody 의 값을 셋팅하여준다.


Spring Boot 기본 설정인 경우, 등록되는 디폴트 메시지 컨버터는 아래와 같다.

0 = {ByteArrayHttpMessageConverter}
1 = {StringHttpMessageConverter}
2 = {ResourceHttpMessageConverter}
3 = {SourceHttpMessageConverter}
4 = {Jaxb2RootElementHttpMessageConverter}
5 = {MappingJackson2HttpMessageConverter}


그런데, 위 메시지 컨버터 중에는 HashMap 을 지원하는 컨버터는 없으며MultiValueMap을 지원하는 메시지 컨버터는 있다.



그렇기 때문에, requestBody 를 HashMap으로 처리하려고하면 안되고, MultiValueMap으로 처리하면 됐던 것이다.


MultiValueMap을 지원하는 디폴트 메시지 컨버터는 FormHttpMessageConverter이며, 

스프링 3.2 부터는 AllEncompassingFormHttpMessageConverter 가 FormHttpMessageConverter 를 상속하여 사용된다.


스프링 레퍼런스에서는 아래와 같이 postForObject 사용예를 들었다.


MappingJacksonValue value = new MappingJacksonValue(new User("eric", "7!jd#h23"));
value.setSerializationView(User.WithoutPasswordView.class);
HttpEntity<MappingJacksonValue> entity = new HttpEntity<MappingJacksonValue>(value);
String s = template.postForObject("http://example.com/user", entity, String.class);



그리고 필자는,

public Map<String, String> test(@RequestBody MultiValueMap<String, String> map) {
RestTemplate template = new RestTemplate();
return template.postForObject("http://111.222.333.444/api/test/", map, Map.class);
}

와 같이 @RequestBody 로 MultiValueMap 으로 받아 처리하도록 하였다..