728x90
반응형

HTTP 메시지 컨버터는 언제 사용되는 것인가

클라이언트에서 서버로 요청 데이터를 전달할 때는 3가지 방법이 있다.

 

첫번째는 GET 메소드를 통한 쿼리 파라미터로 전달하는 방법이다.
이 때 url은 보통 /board?page=1&year=2024 이런식으로 메시지 바디 없이, URL의 쿼리 파라미터에 데이터를 포함해서 전달한다.

 

두번째는 POST 메서드를 통해 HTML form 데이터를 보낼 수 있다. 메시지 바디에 쿼리 파라미터 형식으로 page=1&year=2024 형식으로 전달한다.

첫번째와 두번째 방법의 공통점은 둘 다 요청 데이터를 request.getParameter()로 데이터를 읽어서 처리할 수 있다는 것이다.


세번째 방법은 HTTP message body에 데이터를 직접 담아서 요청하는 방법이다.

이 방법을 사용하게 될 때 HTTP 메시지 컨버터가 사용되게 된다.

 

HTTP message body를 통해 데이터가 직접 넘어오는 경우에는 @RequestParam 이나 @ModelAttribute와는 상관이 없다.
이 2가지 어노테이션은 요청 파라미터를 처리하는 것이고, 세번째 방법과 관련된 어노테이션은 @RequestBody와 @ResponseBody이다.

 

서버에서 클라이언트로 응답 데이터를 만드는 방법도 3가지이다.

 

첫번째는 정적 리소스 자체를 제공해주는 것, 두번째는 뷰 리졸버를 사용하는 뷰 템플릿을 사용하는 방법이다.

 

세번째는 HTML을 제공하는 것이 아니라 JSON 형태의 데이터를 전달하는 HTTP 메시지를 사용하는 방법이 있다.

이 세번째 방법에 HTTP 메시지 컨버터가 사용되게 된다.

 

언제 사용되게 되는지 보았을때 '데이터'를 사용할 때 HTTP 메시지 컨버터가 사용되는 것으로 보인다.
즉, HTTP 메시지 컨버터는 메시지 바디의 내용을 우리가 원하는 문자(String)이나 객체 등으로 변환해준다.

HTTP 메시지 컨버터는 왜 사용하는 것인가

만약에 JSON 데이터를 메시지 바디에 넣어서 요청을 하거나 응답을 할 때 사용한다고 생각해보자.

 

먼저 요청의 경우, 클라이언트가 보낸 JSON 데이터를 읽어서 서버가 처리를 하려면 스트림 처리를 해야된다.
즉, requestStream을 읽어서 직접 JSON으로 변환을 했어야 한다.

 

또한 응답의 경우도 JSON 데이터를 HTTP 메시지 바디에 넣어서 반환을 하려면 response.getWriter()로 직접 넣는 작업이 필요하다.


이런 부분이 불편하기 때문에 스프링은 HTTP 메시지 컨버터라는 것을 제공해준다.

HTTP 메시지 컨버터는 알아서 변환하여 HTTP 메시지 바디를 읽거나 쓰는 것을 도와준다.

HTTP 메시지 컨버터 사용

스프링은 다음 어노테이션의 경우 HTTP 메시지 컨버터를 적용한다.

  1. @RequestBody, HttpEntity(RequestEntity)
  2. @ResponseBody, HttpEntity(ResponseEntity)

HTTP 메시지 컨버터는 요청과 응답 둘 다 사용된다. 컨버터는 양방향이다.

  • canRead() , canWrite() : 메시지 컨버터가 해당 클래스, 미디어 타입을 지원하는지 체크.
  • read() , write() 메시지 컨버터를 통해서 메시지를 읽고 쓰는 기능

@ResponseBody의 사용 원리

만약 @ResponseBody 를 사용한다면

  • HTTP의 body에 문자 내용을 직접 반환한다.
  • viewResolver 대신에 HttpMessageConverter 가 동작하게 된다.
  • 기본 문자처리는 StringHttpMessageConverter 가 처리한다.
  • 기본 객체처리는 MappingJackson2HttpMessageConverter 가 처리한다.
    • 이건 Json으로 바꿔주는 ObjectMapper를 통해서 객체가 json으로 바뀌어서 응답 메시지에 넣어져서 나간다.
  • 이외에 byte 처리 등등 기타 여러 HttpMessageConverter가 기본으로 등록되어 있다.

클라이언트의 HTTP Accept 헤더 서버의 컨트롤러 반환 타입 정보 둘을 조합해서 HttpMessageConverter 가 선택된다.

HTTP 메시지 컨버터의 구성

HTTP 메시지 컨버터는 인터페이스로 되어있다.

이유는 Json으로 처리해주는 컨버터, String으로 처리해주는 컨버터 등등 여러가지가 있기 때문이다.


그래서 위에서 말한대로 응답의 경우 HTTP Accept 헤더와 서버의 컨트롤러 반환 타입 정보를 확인해서 선택하게 된다.


String은 StringHttpMessageConverter, Json은 MappingJackson2HttpMessageConverter가 각각 처리해준다.

 

스프링 부트는 다양한 메시지 컨버터를 제공하는데, 대상 클래스 타입 미디어 타입 둘을 체크해서 HTTP 메시지 컨버터의 사용여부를 결정한다.

→ 여기서 미디어 타입이라는 것은 Http 요청 메시지에서 메시지 바디에 있는 content-type이 무슨 타입이라고 알려주는 것이다.

 

예를 들어서 content type이 application/json이다. 그러면 '메시지 바디에 있는게 json 이구나!' 하면서 해당 메시지 컨버터가 선택이 되는 것이다. 그것만 따지는 것이 아니고 대상 클래스의 type 그리고 media-type 둘 다 체크해서 사용 여부를 결정한다.

만약 여기서 만족하지 않으면 canRead, canWrite 를 실행해서 다음 컨버터로 넘어가게 되는 것이다.

(우선순위는 높은 순서대로 0은 Byte, 1은 String, 2는 Json, ..)

 

그래서 항상 HTTP 메시지에 body 데이터가 있다면 그 컨텐츠 타입을 지정해줘야 한다

HTTP 메시지 컨버터가 데이터를 처리하는 과정

HTTP 요청이 오면 컨트롤러에서 @RequestBody나 HttpEntity 파라미터를 사용한다면 메시지 컨버터가 메시지를 읽을 수 있는지 확인하기 위해서 canRead() 를 호출한다.

그럼 먼저 대상 클래스 타입을 지원하는가를 확인하고, HTTP 요청의 Content-Type 미디어 타입을 지원하는가를 확인한다.

 

조건을 만족하면 Read를 호출해서 객체를 생성하고 그 컨트롤러의 파라미터로 넘겨준다.

 

응답 데이터 같은 경우에는 컨트롤러에서 @ResponseBody 이거나 HttpEntity 로 반환이 되면 메시지 컨버터가 메시지를 쓸 수 있는지 확인하기 위해서 canWrite() 를 호출한다.

호출해서 대상 클래스 타입을 지원하는가를 확인하고, HTTP 요청의 Accept 미디어 타입을 지원하는가를 같이 보게 된다.

 

즉, 요청의 경우에는 Content-Type에 Media-Type을 확인하고 응답이 나갈때는 HTTP 요청 메시지에 있는 Accept 미디어 타입을 지원하는지.

(클라이언트가 읽을 수 있는 메시지를 서버가 줘야하기 때문에 HTTP 요청에 Accept Media Type을 지원하는가도 추가적으로 체크를 하는 것이다. )

그럼 HTTP 메시지 컨버터는 스프링 MVC에서 어디쯤에서 사용되는 것일까?

이것은 @RequestMapping 을 처리하는 핸들러 어뎁터인 @RequestMappingHandlerAdapter와 관련이 있다.

 

HTTP 요청을 Spring이 처리하는 과정을 생각해보면,

먼저 실제로 HTTP 요청이 오게 되면 DispatcherServlet은 핸들러 매핑에게 해당 요청을 처리할 수 있는 핸들러를 조회하도록 위임한다.

그럼 등록되어 있는 핸들러 어댑터 중에 해당 핸들러를 처리할 수 있는 핸들러 어댑터를 조회한다.

 

다음 이 핸들러 어댑터를 통해서 실제 컨트롤러가 호출이 된다. (핸들러를 @Controller에 정의한 요청을 처리하는 처리기라고 생각하면 된다)

 

여기서 나누어 지게 되는데, 핸들러(컨트롤러)의 리턴값을 보고 어떻게 처리할지 판단한다.

 

만약에 View의 경우 뷰 이름에 해당하는 뷰를 찾아서 모델 데이터를 렌더링하는 것이고, @ResponseBody가 있다면 Converter를 사용해서 응답을 처리하게 되는 것이다.

 

어노테이션 기반의 컨트롤러의 여러 파라미터를 만들어서 호출할 수 있는 것이 바로 이 핸들러 어댑터에서 HTTP 메시지 컨버터와 관련이 있는 것이다.

 

그럼 요청 매핑 핸들러 어댑터(@RequestMappingHandlerAdapter)가 어떤 식으로 동작할까?

 

우리가 Spring을 통해서 Controller를 구성할 때, @ModelAttribute 이든, InputStream 이든 HttpEntity 같은 것들을 함수의 파라미터로 사용한다. 그러면 이걸 누군가 이런 함수의 파라미터들을 데이터로 전달해주어야 한다.

 

여기서 ArgumentResolver라는 개념이 나오게 된다.

ArgumentResolver와 HTTP 메시지 컨버터

RequestMappingHandlerAdapter는 ArgumentResolver라는 매개변수를 처리해주는 것이 있다.
(argument가 파라미터, 매개변수를 의미하고 스프링에서 Resolver라는 것이 나오면 뭔가를 처리해주는 것을 의미한다.)

 

생각해보면 어노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있다. 이렇게 파라미터를 유연하게 처리할 수 있는 이유가 바로 ArgumentResolver 덕분이다.

 

핸들러 어댑터 혼자 처리하기 어려워서 ArgumentResolver를 사용해서 필요한 객체들을 생성하게 된다. 그리고 ArgumentResolver가 MessageConverter를 사용하게 되는 것이다.

 

어노테이션 기반 컨트롤러를 처리하는 RequestMappingHandlerAdaptor 는 바로 이 ArgumentResolver 를 호출해서 컨트롤러(핸들러)가 필요로 하는 다양한 파라미터의 값(객체)를 생성한다. 그리고 이렇게 파라미터의 값이 모두 준비되면 컨트롤러를 호출하면서 값을 넘겨준다. 스프링은 30개가 넘는 ArgumentResolver를 기본으로 제공한다.

 

여기서 supportsParameter가 해당 파라미터를 지원하는지 확인한다. 반환타입이 Object이다.

즉, ArgumentResolver의 supportsParameter() 를 호출해서 해당 파라미터를 지원하는지 체크하고, 지원하면 resolveArgument()를 호출해서 실제 객체를 생성한다. 그리고 이렇게 생성된 객체가 컨트롤러 호출 시 넘어가게 되는 것이다.

 

반환할 때도 언제는 View, 언제는 ModelAndView 이런식으로 되게 많은데 이걸 처리해주는 것도 인터페이스가 되어있다. ReturnValueHandler 이다.

ReturnValueHandler도 응답 값을 반환하고 처리한다. 그래서 컨트롤러에서 String으로 뷰 이름만 반환해도 동작하는 이유가 바로 ReturnValueHandler 덕분이다.

 

여기까지 알고 이제 HTTP 메시지 컨버터는 어디서 동작할까? 이건 ArgumentResolver랑 ReturnValueHandler가 사용하는 것이다.

ArgumentResolver는 Argument를 찾는 거고 그 ArgumentResolver들 중에서 HTTP 메시지 바디에 있는 걸 바로 뭔가 처리해야 된다고 하면 메시지 컨버터를 호출한다.

 

스프링은 ArgumentResolver, ReturnValueResolver, MessageConverter 세가지를 모두 인터페이스로 제공한다. OCP 원칙을 지키면서 확장할 수 있다.

 

즉, ArgumentResolver는 Controller에 선언된 Parameter를 생성하는 역할, ArgumentResolver가 MsgConverter를 사용하는 것이다.

728x90
반응형

+ Recent posts