2024.09.13.금.TIL HTTP Message Converter

김기남·2024년 9월 13일
post-thumbnail

안녕하세요, 오늘은 HTTP Message Converter 에 대해 정리해 보았습니다.

HTTP Message Converter

💡 View Template을 사용하여 응답하는것이 아니라 Rest API(HTTP API)로 JSON, TEXT, XML 등의 데이터를 HTTP Message Body에 직접 입력하는 경우 HTTP Message Converter를 사용한다.
  • 참고
    • Server Side Rendering의 경우 @Controller + View Template을 사용하여
      동적으로 화면을 그린다. → Server 측에서 동적으로 그린다.
    • Client Side Rendering의 경우 @RestController + Rest API를 통해 화면을 동적으로 그린다.
      → Client 측에서 동적으로 그린다.
    • 두가지 기술은 함께 사용되는 경우가 많다.
      → 카카오 로그인 실습 html(${folders}) → js(ajax)

JSON을 주로 사용하는 이유

💡 마치 전세계 공통어로 영어를 사용하는것처럼 Web의 세계에서는 JSON(**J**ava**S**cript **O**bject **N**otation)을 공통어로 사용한다.
  • MSA

    • 사진출처 - SKT EnterPrise

  • 해당 아키텍처를 가지게되면 구성된 Application마다 어떠한 언어를 사용하는지에 상관없이
    서로 통신을 할수 있는데 이것이 가능한 이유는 JSON형태로 데이터 통신을하기 때문이다.

  • 그림예시

  • Client → Server 의 경우에도 JSON을 사용한다.

  • Server → Server 의 경우에도 JSON을 사용한다.

  • 각각의 Application들은 JSON 데이터를 해당언어의 Object로 변환시켜주는
    Message Converter가 필요하다!

HTTP Message Converter

  • 동작 순서

  • @ResponseBody 사용

    • ViewResolver가 아닌 HttpMessageConverter가 동작한다
  • HTTP Message Body에 String을 직접 입력 후 반환

  • String : StringHttpMessageConverter

  • JSON : MappingJackson2HttpMessageConverter

  • 이외 기본적으로 만들어놓은 다양한 MessageConverter가 존재한다.

  • Response의 경우 HTTP Request Header의 Accept(허용) 헤더와
    Controller 반환 Type 정보를 조합하여 Message Converter가 만들어진다.

  • Spring에서 HTTP Message Converter가 적용되는 두가지 예시

    1. HTTP Request : @RequestBody, HttpEntity<>, RequestEntity<> 가 사용된 경우(요청)
    2. HTTP Response : @ResponseBody, HttpEntity<>, ResponseEntity<> 가 사용된 경우(응답)
  • HttpMessageConverter 내부구조

  • HttpMessageConverter는 HTTP Request, HTTP Response 모두 사용된다.

  • canRead(), canWrite() 메서드로 클래스, 미디어 타입 지원여부를 체크한다.

boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

read(), write() 메서드로 Http Message 를 읽고 쓴다.

T read(
				Class<? extends T> clazz, 
				HttpInputMessage inputMessage
) throws IOException, HttpMessageNotReadableException;

void write(
						T t, 
						@Nullable MediaType contentType, 
						HttpOutputMessage outputMessage
) throws IOException, HttpMessageNotWritableException;
  • Converter는 양방향 Request ↔ Response 으로 동작한다.

HttpMessageConverter 우선순위

💡 Spring은 다양한 Message Converter를 제공하고 있고 우선순위가 있다. 대상 Class와 MediaType을 체크해서 어떤 Converter를 사용할지 결정한다.
  • 우선순위
    1. ByteArrayHttpMessageConverter
      • byte[] Data를 처리한다.
      • Class Type : byte[]
      • Media Type : /
      • 반환 : application/octet-stream
// 요청 헤더 -> content-type: */*

@PostMapping("/byte[]") // produces = "application/octext-stream"
public byte[] example(@RequestBody byte[] data) {
	// logic
	return data; // response
}
  1. StringHttpMessageConverter
    • String Data를 처리한다.
    • Class Type : String
    • Media Type : /
    • 반환 : text/plain
// content-type: text/plain

@PostMapping("/string") // produces = "text/plain"
public String example(@RequestBody String data) {
	// logic
	return data;
}
  1. MappingJackson2HttpMessageConverter
    • JSON Data를 처리한다.
    • Class Type : Object, HashMap
    • Media Type : application/json …
    • application/json …
// content-type: application/json

@PostMapping("/json") // produces = "application/json"
public Data example(@RequestBody Data data) {
	// logic
	return data;
}
  1. …기타 등등

Http Request Read

  1. Controller에서 @RequestBody 혹은 HttpEntity<>로 파라미터 바인딩한다.
  2. MessageConverter는 canRead() 메서드로 읽기가능 여부를 조회한다.
    1. 대상 class가 byte[], String, Object 인지 여부 확인
    2. Http Request Header의 Content-Type Media Type 지원여부 확인(consumes)
boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

read() 메서드를 호출하여 Object를 생성한다.

T read(
				Class<? extends T> clazz, 
				HttpInputMessage inputMessage
) throws IOException, HttpMessageNotReadableException;

Http Response Write

  1. Controller에서 @ResponseBody 혹은 HttpEntity로 응답이 반환된다.
  2. MessageConverter는 canWrite() 메서드로 사용가능 여부를 조회한다.
    1. 반환 class가 byte[], String, Object 인지 여부 확인
    2. Http Request Header의 Accept Media Type 지원여부 확인(produces)
boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

write() 메서드를 호출하여 HTTP Response Message Body에 데이터를 입력한다.

void write(
						T t, 
						@Nullable MediaType contentType, 
						HttpOutputMessage outputMessage
) throws IOException, HttpMessageNotWritableException;
  • 참고: produces를 따로 설정하지 않으면 Request Accept Header의 값으로 Default 값이 설정된다.

추가예시1 (성공)

💡 Request Header의 content-type(json)과 @RequestBody에 class(String)이 오게되면 어떻게 될까?
// content-type: application/json
// {"key": "value"}

@PostMapping("/json-to-text") // produces = "text/plain"
public String example(@RequestBody String data) { // "{\"key\": \"value\"}"
	// logic
	return data;
}
  1. ByteArrayHttpMessageConvertercanRead() 메서드 호출해보고 false 지나간다.
  2. StringHttpMessageConvertercanRead() 메서드 호출한다.

  • 지원하는 Media Type 이 전체이다.
  • StringHttpMessageConverter가 동작한다.
  • 우선순위에 밀려서 MappingJackson2HttpMessageConverter 는 동작하지 않는다.

추가예시2 (성공)

💡 Request Header의 content-type(json)과 @RequestBody에 class(Data)가 오게되면 어떻게 될까?
// content-type: application/json
// {"key": "value"}

@PostMapping("/json-to-json") // produces = "application/json"
public String example(@RequestBody Data data) { // // {"key": "value"}
	// logic
	return data;
}
  1. ByteArrayHttpMessageConvertercanRead() 메서드 호출해보고 false 지나간다.
  2. StringHttpMessageConvertercanRead() 메서드 호출해보고 false 지나간다.
  3. MappingJackson2HttpMessageConvertercanRead() 메서드를 호출한다.

추가예시3 (실패)

💡 Request Header의 content-type(text/html)과 @RequestBody에 class(Data)가 오게되면 어떻게 될까?
@Data
public class Data {
	private String key;
}

// content-type: text/html
// // "{\"key\": \"value\"}"

@PostMapping("/text-to-json") // produces = "application/json"
public String example(@RequestBody Data data) {
	// logic
	return data;
}
  1. ByteArrayHttpMessageConvertercanRead() 메서드 호출해보고 false 지나간다.
  2. StringHttpMessageConvertercanRead() 메서드 호출해보고 false 지나간다.
  3. MappingJackson2HttpMessageConvertercanRead() 메서드를 호출해보고 false
  4. 모든 MessageConverter가 동작하지 못한다.

Argument Resolver


💡 HTTP Message Converter는 Spring의 어디에서 존재하고 동작하는가?

Spring MVC 구조

  1. Request가 Controller에 전달되는 Handler Adapter와 Handler 사이에서 어떤 일이 일어난다!
  2. @RequestMapping 을 처리하는 HandlerAdapter의 구현체인
    RequestMappingHandlerAdapter가 어떤 일을 해준다!

RequestMappingHandlerAdapter

💡 `@RequestMapping`을 처리하는 Handler Adapter이다.

동작방식

Argument Resolver

  1. Argument = 매개변수 = 파라미터 모두 같은말이다.
  2. HttpServletRequest, Model, HttpEntity,
    @ModelAttribute, @RequestBody, @RequestParam
    다양한 파라미터 바인딩을 할 수 있는 이유는 바로 Argument Resolver 때문이다.
  3. RequestMappingHandlerAdapter는 Argument Resolver를 호출하여
    Controller가 필요한 다양한 파라미터의 값을 생성한다.
  4. Argument Resolver를 통하여 값이 준비되면 해당값을 가지고 실제 Controller를 호출한다.
  5. Spring은 다양한 Argument Resolver들을 기본적으로 제공한다.
    1. ex) RequestBodyArgumentResolver(@RequestBody 파라미터 바인딩시 사용)
![](https://velog.velcdn.com/images/maltize92/post/9a724d2c-2966-447b-95da-9530ebeeedea/image.png)

ex) RequestHeaderArgumentResolver(@RequestHeader)

  • HandlerMethodArgumentResolver(Argument Resolver의 실제 이름)

    • 인터페이스로 구성되어 있다. 즉, implements 하여 커스텀하게 파라미터를 만들 수 있다.(확장)

    • supportsParameter(MethodParameter parameter);

      • 위 코드로 컨트롤러가 필요로하는 메서드의 파라미터를 지원하는지 여부를 검사한다.
    • 지원한다면 resolveArgument() 메서드를 통해 Object(객체)로 만들어준다.

    • 만들어진 Object(객체)가 Controller 호출시 메서드의 파라미터로 전달된다.

    • supportsParameter()를 사용하는곳들

ReturnValueHandler

  1. ModelAndView, @ResponseBody, HttpEntity<> 등이 있으면 응답에 필요한 값으로 변환해준다.

  2. Controller에서 String으로 View Name 을 반환하여도 View가 동작하는 이유

  3. Spring은 다양한 ReturnValueHandler를 기본적으로 제공한다.

    1. ex) ModelAndViewMethodReturnValueHandler(ModelAndView 객체 반환)

    2. ex) HttpEntityMethodProcessor(HttpEntity 객체 반환)

  • HandlerMethodReturnValueHandler (ReturnValueHandler의 실제 이름)
    • 인터페이스로 구성되어 있다. 즉, 확장이 가능하다.

HTTP Message Converter

💡 HTTP Message Converter는 어디에서 동작하는가? HTTP Message Converter를 사용하는 어노테이션 `@RequestBody`, `@ResponseBody`
  • RequestMappingHandlerAdapter 구조

  • 위에서 보았던 RequestMappingHandlerAdapter 구조에서 HTTP Message Converter는
    보이지 않는다. 어디쯤에 있는것인가?

  • 실제 구조

  • HTTP Message Converter는 요청시에는 Argument Resolver가 사용하는것이다.
  • 응답시에는 ReturnValueHandler가 사용한다.

참고

  1. @RequestBody, @ResponseBody 어노테이션이 존재하면
    RequestResponseBodyMethodProcessor 라는 Argument Resolver가 동작한다.

- `supportsParameter()` 존재. 설명은 아래
- `resolveArgument()` 존재. 설명은 아래

- `RequestResponseBodyMethodProcessor` Class Diagram

  • HandlerMethodArgumentResolver 상속. 설명은 아래
  • HandlerMethodReturnValueResolver 상속.
    @RequestBody, @ResponseBody 두가지 모두 처리하기위해 둘다 상속받음
  1. HttpEntity가 있으면 HttpEntityMethodProcessor 라는 Argument Resolver가 동작한다.

HttpEntityMethodProcessor의 class diagram

  • HandlerMethodArgumentResolver가 부모 클래스에 존재한다.

  • 즉, HandlerMethodArgumentResolver 는 HttpEntity를 처리하는 Argument Resolver이다.

  • HttpEntityMethodProcessor.supportParameter()

  • HttpEntity 혹은 RequestEntity(HttpEntity를 상속받은 객체)의 경우인지 체크한다.

  • HttpEntityMethodProcessor.resolveArgument()

  • 실제 Object로 만들어주는 메서드
  • readWithMessageConverters(webRequest, parameter, paramType);
  • 위 코드를 실행하여 코드 내부에서 MessageConverter 호출하여 값을 처리한다.

정리

💡 Argument Resolver와 Http Message Converter는 다른것이다! → 사용하는것 ReturnValueHandler가 Http Message Converter를 사용한다.

Request (요청시)

  • @RequestBody 를 처리하는 ArgumentResolver가 존재한다.
  • HttpEntity를 처리하는 ArgumentResolver가 존재한다.
  • 다양한 ArgumentResolver들이 HTTP Message Converter를 호출하여
    (read하여) 필요한 Object로 변환한다.

Response (응답시)

  • @ResponseBody 를 처리하는 ReturnValueHandler가 존재한다.
  • HttpEntity를 처리하는 ReturnValueHandler가 존재한다.
  • 다양한 ReturnValueHandler 들이 HTTP Message Converter를 호출하여
    (write하여) 필요한 응답을 입력한다.

기능의 확장


  • Spring에서 아래의 클래스 들은 모두 Interface로 구성되어있다.
    즉, 커스텀하게 확장이 가능하다.
  1. HandlerMethodArgumentResolver Argument Resolver
  2. HandlerMethodReturnValueHandler Return Value Handler
  3. HttpMessageConverter
  • 하지만 필요한 기능중 대부분은 Spring에서 기본적으로 제공하고 있다.

WebMvcConfigurer

  • 인터페이스
  • 기능 확장이 필요한 경우 WebMvcConfigurer를 상속받아
    Spring Bean으로 (@Component, @Configuer) 등록 해주면된다.
  • 필요할 때 검색하여 찾아보아도 무방하다!
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.springframework.web.servlet.config.annotation;

import java.util.List;
import org.springframework.format.FormatterRegistry;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.validation.MessageCodesResolver;
import org.springframework.validation.Validator;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.method.support.HandlerMethodReturnValueHandler;
import org.springframework.web.servlet.HandlerExceptionResolver;

public interface WebMvcConfigurer {
    default void configurePathMatch(PathMatchConfigurer configurer) {
    }

    default void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
    }

    default void configureAsyncSupport(AsyncSupportConfigurer configurer) {
    }

    default void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
    }

    default void addFormatters(FormatterRegistry registry) {
    }

    default void addInterceptors(InterceptorRegistry registry) {
    }

    default void addResourceHandlers(ResourceHandlerRegistry registry) {
    }

    default void addCorsMappings(CorsRegistry registry) {
    }

    default void addViewControllers(ViewControllerRegistry registry) {
    }

    default void configureViewResolvers(ViewResolverRegistry registry) {
    }

    default void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
    }

    default void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> handlers) {
    }

    default void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    default void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
    }

    default void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    default void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> resolvers) {
    }

    @Nullable
    default Validator getValidator() {
        return null;
    }

    @Nullable
    default MessageCodesResolver getMessageCodesResolver() {
        return null;
    }
}
  1. addArgumentResolvers()
  2. addReturnValueHandlers()
  3. extendMessageConverters()

필요한 메서드를 오버라이딩 하면된다.

  • @Component → Spring 빈등록
    • 해당 어노테이션을 사용하면 Bean Configuration 설정 파일에
      따로 Bean 등록을 하지 않아도 사용할 수 있다.
  • @Configuration
    • @Component 를 포함하고 있다. (Spring Bean 등록이 된다.)

Spring Bean → 실제로 면접에서 나오기도한다. 스프링 빈의 생명주기, 빈 스코프

오늘의 코드카타

import java.util.Stack;

public class Solution {

    public int[] solution(int[] arr) {
        Stack<Integer> st = new Stack();
        for (int i = 0; i < arr.length; i++) {
            if (st.empty() || st.peek() != arr[i]) {
                st.push(arr[i]);
            }
        }
        int[] answer = new int[st.size()];
        for (int i = st.size() - 1; i >= 0; i--) {
            answer[i] = st.pop();
        }
        return answer;
    }
}
import java.util.Arrays;

public class Main {

    public static void main(String[] args) {

        Solution solution = new Solution();

        int[] a1 = {1, 1, 3, 3, 0, 1, 1};
        int[] a2 = {4, 4, 4, 3, 3};

        System.out.println(Arrays.toString(solution.solution(a1)));
        System.out.println(Arrays.toString(solution.solution(a2)));
    }
}
profile
새로운 시작~!

0개의 댓글