HTTP 메시지 컨버터 - http 메시지는 어떻게 파싱될까?!

들어가며


HTTP 메시지는 이렇게 생겼다.

body에 담긴 내용을 가져오는 어노테이션은 @RequestBody이 있고,
header+body내용을 가져올 수 있는 데이터타입으로는 HttpEntity가 있다.

저 HTTP 메시지를 잘 파싱해서, 원하는 타입으로 만들어 컨트롤러 함수의 파라미터로 넣어주는 과정에 대해 알아보자!
(== @RequestBody, HttpEntity가 컨트롤러 함수 파라미터에 있을 때, 어떻게 동작할까?!)

HttpMessageConverter 인터페이스

스프링은 이 상황에 필요한 인터페이스를 준비해두었다.
바로바로, HttpMessageConverter!

public interface HttpMessageConverter<T> {
    boolean canRead(Class<?> var1, MediaType var2); // 미디어 타입을 체크해서, 읽을 수 있는지 검사

    boolean canWrite(Class<?> var1, MediaType var2); // 미디어 타입을 체크해서, 쓸 수 있는지 검사

    List<MediaType> getSupportedMediaTypes();

    T read(Class<? extends T> var1, HttpInputMessage var2) throws IOException, HttpMessageNotReadableException; // 메시지 컨버터를 통해서 메시지를 읽음

    void write(T var1, MediaType var2, HttpOutputMessage var3) throws IOException, HttpMessageNotWritableException; // 메시지 컨버터를 통해서 메시지를 씀
}

인터페이스를 봐서 유추할 수 있듯이, 미디어 타입에 따라 사용할 수 있는 여러 구현체 또한 준비해두었다.

0 = ByteArrayHttpMessageConverter
1 = StringHttpMessageConverter
2 = MappingJackson2HttpMessageConverter
// 일부 생략

*read 기준으로 작성

클래스처리하는 클래스 타입미디어 타입예시
ByteArrayHttpMessageConverterbyte []*/*@RequestBody byte[] burrito
StringHttpMessageConverterString*/*@RequestBody String burrito
MappingJackson2HttpMessageConverter객체, HashMapapplication/json@RequestBody Burrito burrito

HttpMessageConverter 동작 방식

@RequestBody를 처리하는 HandlerMethodArgumentResolver인 RequestResponseBodyMethodProcessor 의 함수를 살펴보자!
(HandlerMethodArgumentResolver에 대해 궁금하다면, [링크추가해야함] 참고!)

@Override
	public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,
			NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {

		Object arg = readWithMessageConverters(webRequest, parameter, parameter.getGenericParameterType()); // 메시지 컨버터를 활용해서 클라이언트 요청을 파싱
		String name = Conventions.getVariableNameForParameter(parameter);

		WebDataBinder binder = binderFactory.createBinder(webRequest, arg, name);
		if (arg != null) {
			validateIfApplicable(binder, parameter);
			if (binder.getBindingResult().hasErrors() && isBindExceptionRequired(binder, parameter)) {
				throw new MethodArgumentNotValidException(parameter, binder.getBindingResult());
			}
		}
		mavContainer.addAttribute(BindingResult.MODEL_KEY_PREFIX + name, binder.getBindingResult());

		return arg;
	}

readWithMessageConverters() 함수부터 파고파고 들어가보면... 익숙한 흐름을 찾을 수 있다.

스프링이 미리 등록해둔 구현체를 순차적으로 탐색하면서, 클라이언트 요청으로 넘어온 HTTP 메시지를 처리할 수 있는 메시지 컨버터를 찾는다.

canRead() 함수가 미디어타입과 클래스타입을 체크해서 참을 반환하면, read() 함수를 통해 파싱을 진행한다.

for (HttpMessageConverter<?> converter : this.messageConverters) {
				Class<HttpMessageConverter<?>> converterType = (Class<HttpMessageConverter<?>>) converter.getClass();
				if (converter instanceof GenericHttpMessageConverter) {
					GenericHttpMessageConverter<?> genericConverter = (GenericHttpMessageConverter<?>) converter;
					if (genericConverter.canRead(targetType, contextClass, contentType)) {
						if (logger.isDebugEnabled()) {
							logger.debug("Read [" + targetType + "] as \"" + contentType + "\" with [" + converter + "]");
						}
						if (inputMessage.getBody() != null) {
							inputMessage = getAdvice().beforeBodyRead(inputMessage, param, targetType, converterType);
							body = genericConverter.read(targetType, contextClass, inputMessage);
							body = getAdvice().afterBodyRead(body, inputMessage, param, targetType, converterType);
						}
						else {
							body = null;
							body = getAdvice().handleEmptyBody(body, inputMessage, param, targetType, converterType);
						}
						break;
					}
				}
// 이하 생략

HttpMessageConverter 는 요청 처리 과정 중 어디쯤에서 사용될까?!

HandlerMethodArgumentResolver 를 통해 컨트롤러 함수의 파라미터를 셋팅하는 방법에 대해 알아볼 때 봤던 흐름이다.

HttpMessageConverter는 이 과정에서 쓰인다.
(사실 처음부터 스포가 있었다...)

HttpMessageConverter는 어디에 쓰이는가?
HTTP 메시지를 파싱할 때 쓴다.

HTTP 메시지를 기반으로 파라미터를 셋팅할 때는 언제인가?
@RequestBody 어노테이션이 붙어있을 때, HttpEntity의 변수가 파라미터로 있을 때!

따라서, 이 두 가지 경우를 처리하는 HandlerMethodArgumentResolver에서 파라미터로 넘길 값을 셋팅하는 과정인 resolveArgument() 에서 쓰인다!

  • @RequestBody -> RequestResponseBodyMethodProcessor
  • HttpEntity -> HttpEntityMethodProcessor

도식화 하면 이런 흐름이다.

출처

  1. HTTP 메시지 구조 이미지: https://ohcodingdiary.tistory.com/5
  2. 전체적인 내용의 기반: 인프런 강의(스프링 MV 1편 - 백엔드 웹 개발 핵심 기술)

좋은 웹페이지 즐겨찾기