Spring MVC : HTTP 메세지 컨버터

    HTTP 메세지 컨버터


    HTTP API처럼 JSON 데이터를 HTTP 메세지 바디에서 직접 읽거나 쓰는 경우 HTTP 메세지 컨버터를 사용하면 편리하다고 한다. 스프링에서 HTTP 메세지 컨버터는 다음과 같은 경우에 사용된다.

    • HTTP 요청 : @RequestBody, @RestController, HttpEntity, RequestEntity
    • HTTP 응답 : @ResponseBody, @RestController, HttpEntity, ResponseEntity

    HTTP 메세지 컨버터는 Interface이고, 따라서 여러 구현체들이 존재한다. 이 중에서 어떤 메세지 컨버터를 사용할지는 클라이언트가 보낸 HTTP Accepter 헤더, Content-type을, 서버의 컨트롤러 반환 타입을 조합해서 HTTP 메세지 컨버터가 선택된다고 한다. 

    아래에 한 예를 살짝 살펴보고자 한다. 

    • @ResponseBody를 사용하면 HTTP 메세지 바디에 직접 데이터가 전달된다.
    • @ResponseBody 어노테이션을 확인하면,  ViewResolver 대신 HTTP Message Converter가 대신 동작한다.
    • 여러 HttpMessageConverter가 구현되어있다.
      • 기본 문자처리 : StringHttpMessageConverter
      • 기본 객체처리 : MappingJackson2HttpMessageConverter

     

     

    HTTP 메세지 컨버터는 인터페이스다! 


    public interface HttpMessageConverter<T> {
    
       boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
    
       boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);
    
       List<MediaType> getSupportedMediaTypes();
    
       default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
          return (canRead(clazz, null) || canWrite(clazz, null) ?
                getSupportedMediaTypes() : Collections.emptyList());
       }
    
       T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
             throws IOException, HttpMessageNotReadableException;
    
       void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
             throws IOException, HttpMessageNotWritableException;
    
    }
    • HTTP 메세지 컨버터는 인터페이스로 작성되어있다. 
      • 따라서 이를 구현한 여러가지 구현체 컨버터들이 만들어진다.
    • CanRead(), CanWrite() : 메세지 컨버터가 해당 클래스, 미티어 타입을 지원하는지 체크
    • read(), write() : 메세지 컨버터를 통해서 메세지를 읽고 쓰는 기능

     

    스프링부트가 제공하는 기본 메세지 컨버터를 알아보자

    • 0순위 : ByteArrayHttpMessageConverter
    • 1순위 : StringHttpMessageConverter
    • 2순위 : MappingJackson2HttpMessageConverter

    스프링부트는 HttpMessageConverter를 구현한 다양한 메세지 컨버터를 제공한다. 각 컨버터는 대상이 되는 클래스, 미디어 타입을 지원하는지를 확인해서 사용여부를 결정한다. 둘다 만족하지 않으면 다음 메세지 컨버터를 살펴보게 된다. 

    ByteArrayHttpMessageConverter

    • byte[] 데이터를 처리한다.
    • 클래스 타입 : byte[]
    • 미디어 타입 : */*
    • 요청 : @RequestBody byte[] data
    • 응답 : @ResponseBody return byte[], 쓰기 미디어타입 application/octet-stream

     

    StringHttpMessageConverter

    • String 문자로 데이터를 처리한다.
    • 클래스타입 : String
    • 미디어 타입 : */*
    • 요청 : @RequestBody String data
    • 응답 : @ResponseBody return "ok", 쓰기 미디어타입 text/plain

     

    MappingJackson2HttpMessageConverter

    • application/json 타입의 데이터를 처리한다.
    • 클래스타입 : 객체, HashMap
    • 미디어타입 : applicatino/json
    • 요청 : @RequestBody HelloData data
    • 응답 : @ResponseBody return HelloData, 쓰기 미디어 타입 : applicatino/json 관련

     

    예시, 아래 요청이 오면 어떤 메세지 컨버터가 대응할까?

    1. 클래스 타입이 Byte[]가 아니니 ByteArrayHttpMessageConverter는 아니다
    2. 클래스 타입이 String이 아니니 StringHttpMessageConverter는 넘어간다.
    3. 클래스 타입이 객체이므로, MappingJackson2HttpMessageConverter에서 미디어 타입을 한번 더 체크한다. 
    4. 미디어 타입 체크 시, text/html이기 때문에 MappingJackson2HttpMessageConverter를 사용할 수 없는 것을 알았다.
    5. 다른 형태의 HttpMessageConverter를 사용하게 될 것이다. 

     

    HttpMessageConverter의 동작 방식 예시


    HttpMessageConverter의 요청 읽기(@RequestBody, HttpEntity, RequestEntity)

    • MessageConverter가 읽을 수 있는지 확인하기 위해 canRead()를 호출한다.
      • 대상 클래스 타입을 지원하는가? 
        • 예) @RequestBody의 대상 클래스(byte [], String, HelloData)
      • HTTP 요청의 Content-Type 미디어 타입을 지원하는가
        • 예) text/plain, application/json, */*
    • 특정 HttpMessageConverter가 canRead()를 만족하면, 이 메세지 컨버터로 read()를 호출해서 객체를 생성하고 반환한다.

    HttpMessageConver의 응답 데이터 생성(@ResponseBody, HttpEntity, ResponseEntity)

    • 컨트롤러에서 @ResponseBody, HttpEntity, ResponseEntity로 값이 반환된다.
    • HttpMessageConverter가 메세지를 쓸 수 있는지 확인하기 위해 canWriter()를 호출한다
      • 대상 클래스 타입을 지원하는지 확인
        • 예) return의 대상 클래스 (byte[], String, HelloData)
      • HTTP 요청의 Accept 미디어 타입을 지원하는지 확인(정확히는 @RequestMapping의 Produces)
        • 예) text/plain, application/json, */*
    • canWrite() 조건을 만족하면 write()를 호출해서 HTTP 응답 메세지 바디에 데이터를 생성한다.

     

    요청 맵핑 핸들러 어댑터 구조 알아보기


    앞에서 HTTP 메세지 컨버터에 대해서 이야기를 해왔다. 그런데 궁금한 점이 있다. HTTP 메세지 컨버터는 어느 단에서 자신의 일을 해주고 있는 것일까? 위의 이미지를 살펴보면 어디에도 HttpMessageConverter의 흔적이 없다. 도대체 얘는 어디에 있는 것일까? 

     

    HttpMessageConverter는 RequestMappingHandlerAdapater에 있다! 


    위는 RequestMappingHandlerAdapater가 동작하는 방식을 작성한 것이다.

    ArgumentResolver

    우리가 사용하는 어노테이션 기반의 컨트롤러는 다양한 파라미터를 사용할 수 있었다. HttpServletRequest, Model, @RequestParam, @ModelAttribute, @RequestBody, HttpEntity 등등. 정말 유연하게 처리를 해주고 있었다. 유연하게 파라미터를 처리할 수 있는 이유는 Argument Resolver 때문이다. 

    RequestMappingHandlerAdapter는 ArgumentResolver를 호출해서 컨트롤러가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. 그리고 파라미터의 값을 다 만들면 RequestMappingHandlerAdapter는 컨트롤러를 호출하면서 값을 넘겨준다. 

    HandlerMethodArgumentResolver == ArgumentResolver 코드 살펴보기

    public interface HandlerMethodArgumentResolver {
    
       boolean supportsParameter(MethodParameter parameter);
    
    	@Nullable
       Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
             NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
    
    }
    • 위는 우리가 ArgumentResolver라고 부르는 인터페이스다.
    • 인터페이스를 살펴보면 supportParameter를 통해서 해당 파라미터를 지원하는지를 체크한다.
    • 해당 파라미터를 지원하면 resolverArgument로 넘어간다. 그리고 반환되는 타입은 Object인 것을 알 수 있다. 
      • 객체를 만들어주는 이유는 Object는 어떤 타입으로도 바뀔 수 있기 때문이다.
    • 사용자가 직접 ArgumentResolver를 구현해서 사용할 수도 있다. 

     

    ReturnValueHandler (HandlerMethodReturnValueHandler)

    ArgumentResolver와 유사한 일을 한다. ReturnValueHandler는 반환값을 확인하고, 필요한 형태의 객체로 변환하고 처리해준다. Controller에서 String으로 ViewName을 반환해도 동작하는 이유가 바로 ReturnValueHandler가 있기 때문이다.

    HandlerMethodArgumentResolver 인터페이스

    public interface HandlerMethodArgumentResolver {
       
       boolean supportsParameter(MethodParameter parameter);
    
       @Nullable
       Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
             NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception;
    
    }
    • ReturnValueHandler도 마찬가지로 인터페이스를 제공한다.
    • supportParameter를 이용해 반환된 타입을 지원하는 Resolver인지 확인한다.
    • 반환된 타입을 지원한다면 resolverArgument 메서드를 실행해서 필요한 값을 만들어 Object Type으로 반환해준다.
    • 마찬가지로 이 인터페이스를 개발자가 직접 구현해서 필요한 형태로 만들 수도 있다. 

     

    HTTP 메세지 컨버터의 위치는 어디에 있을까?


    HTTP 메세지 컨버터는 Argument Resolver에도 있고, ReturnValueHandler에도 존재한다. 그리고 @ResponseBody, @RequestBody, HttpEntity의 경우에 Argument Resolver, Return Value Handler들이 HTTP 메세지 컨버터를 이용한다.

     

    요청의 경우 

    @ReqestBody를 처리하는 ArgumentResolver도 있고, HttpEntity를 처리하는 ArgumentResolver도 존재한다. 기본적으로는 ArgumentResolver들이 모든 데이터를 만들고 반환해주지만, 위 ArgumentResolver들은 HTTP 메세지 컨버터를 사용해서 필요한 객체를 생성한다.

    이 때, 특정 HTTP 메세지 컨버터와 맞는지 확인하기 위해 Loop 문으로 CanRead()를 돌리면서 확인하고, 사용가능한 HTTP 메세지 컨버터를 찾고 거기서 데이터를 만들어서 넘겨준다.

     

    응답의 경우

    @ResponseBody, HttpEntity를 처리하는 ReturnValueHandler가 각각 존재한다. 그리고 이 Handler들은 HTTP 메세지 컨버터를 호출해서 필요한 응답 결과를 만들어서 RequestMappingHandlerAdapter에게 돌려준다.

     

    참고 (사용되는 Argument Resolver는?)

    • @RequestBody, @ResponseBody가 있으면 RequestResponseBodyMethodProcess(Argument Resolver)를 사용한다. 
    • HttpEntity가 있으면 HttpEntityMethodProcess(Argument Resolver)를 사용한다. 

     

     

     

    댓글

    Designed by JB FACTORY