HTTP 메세지 컨버터

OneTwoThree·2023년 10월 7일
0

출처 : 김영한 님 강의


스프링 MVC는 다음 경우에 HTTP 메세지 컨버터를 적용한다
HTTP 요청 : @RequestBody,HttpEntity(RequestEntity)
HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)
즉 HTTP 바디에서 직접 메세지를 읽거나 쓸 때 사용한다.

HTTP 메세지 컨버터 인터페이스가 있다.
HTTP 요청, 응답에 둘다 사용된다.

canRead와 canWrite는 메세지 컨버터가 해당 미디어타입을 지원하는지 체크한다.
read, write를 통해 메세지를 읽고 쓸 수 있다.

스프링부트 기본 메세지 컨버터들과 우선순위는 위와 같다.
대상 클래스 타입, 미디어 타입을 체크해서 사용여부를 결정한다.
만족하지 않으면 다음 메세지 컨버터로 우선순위가 넘어간다.

ByteArrayHttpMessageConverter는
클래스타입 : byte[], 미디어타입 : */*
StringHttpMessageConverter는
클래스타입 : String , 미디어타입 : */*
MappingJackson2HttpMessageConverter는
클래스타입 : 객체, HashMap, 미디어타입 : application/json
일 때 동작한다.

HTTP 요청 데이터를 읽을 때는 컨트롤러에서 @RequestBody HttpEntity 파라미터를 사용할 때 동작한다.

메세지 컨버터가 메세지를 읽을 수 있는지 확인하기 위해 canRead()를 호출한다.(클래스타입과 HTTP요청의 미디어 타입을 지원하는지)
canRead()를 만족하면 read()를 호출해서 객체를 생성하고 반환한다.

HTTP 응답 데이터를 생성할 때는 컨트롤러에서 @ResponseBody, HttpEntity로 값을 반환한다.
마찬가지로 클래스 타입과 HTTP 요청의 Accept 미디어 타입을 지원하는지 확인하기 위해 canWrite()를 호출한다.
조건을 만족하면 write()를 호출해서 HTTP 응답 메세지 바디에 데이터를 생성한다.


✅ ArgumentResolver

@RequestMapping을 처리하는 핸들러 어댑터인 (애노테이션 기반의 컨트롤러를 처리하는) RequestMappingHandlerAdapter는 위와 같이 동작한다.

애노테이션 기반의 컨트롤러는 매우 다양한 파라미터를 사용할 수 있는데, ArgumentResolver 덕분이다.
RequestMappingHanlderAdapter는 ArgumentResolver를 통해서 컨트롤러가 필요로 하는 다양한 파라미터의 값을 생성한다.

스프링에는 다양한 종류의 ArgumentResolver가 있다.

ArgumentResolver 인터페이스는 위와 같다.
supportsParameter로 파라미터를 지원하는지 확인한다.
지원하면 resolveArgument를 호출해서 실제 객체를 생성한다.
이렇게 생성된 객체가 컨트롤러 호출 시 넘어간다.

인터페이스를 확장해서 원하는 ArgumentResolver를 만들 수 있다.

@Component
@RequiredArgsConstructor
public class LoggedInArgumentResolver implements HandlerMethodArgumentResolver {

    private final JwtTokenProvider jwtTokenProvider;

    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(LoggedInMember.class)
                && Long.class.isAssignableFrom(parameter.getParameterType());
    }

    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception {
        String headerAuthorization = webRequest.getHeader(HttpHeaders.AUTHORIZATION);
        LoggedInMember loggedInMember = parameter.getParameterAnnotation(LoggedInMember.class);
        if (!loggedInMember.required() && headerAuthorization == null) {
            return null;
        }
        validExistAccessTokenInHeader(headerAuthorization);
        String accessToken = headerAuthorization.split(" ")[1];
        return jwtTokenProvider.getMemberId(accessToken);
    }

    private void validExistAccessTokenInHeader(String headerAuthorization) {
        if(headerAuthorization == null) throw new NotExistAccessTokenException();
    }

}

프로젝트에서 로그인을 맡은 팀원이 작성한 ArgumentResolver이다.
supportsParameter에서 파라미터에 로그인 한 멤버를 나타내는 애노테이션인 @LoggedInMember가 있는지 확인한다.
조건을 만족하면 resolveArgument로 넘어간다. ResolveArgument에서는 원하는 값을 반환해준다. 위 예시의 경우 애노테이션의 required 속성값이 false고 헤더에 토큰이 없으면 null을 반환한다.
아닐 경우 헤더에서 값을 가져와서 memberId를 반환한다.

✅ ReturnValueHandler

HandlerMethodArgumentResolver를 줄여서 ReturnValueHandler라 부른다.
ArgumentResolver와 비슷한데 응답 값을 변환하고 처리한다.
ReturnValueHandler가 응답 값을 변환해주기 때문에 컨트롤러에서 String 으로 뷰 이름을 반환해도 동작하는 것이다.

✅ Http 메세지 컨버터

HTTP 메세지 컨버터는 ArgumentResolver(요청 시)와 ReturnValueHandler(응답 시)에서 쓰인다.

  • 요청 : @RequestBodyHttpEntity를 처리하는 ArgumentResolver가 있는데 이 ArgumentResolver들에서 각각 HTTP 메세지 컨버터를 사용해서 필요한 객체를 생성한다.
  • 응답 : @ResponseEntityHttpEntity를 ReturnValueHandler에서 처리하는데 여기에서 HTTP 메세지 컨버터를 사용해서 응답 값을 만든다.

스프링은 HandlerMethodArgumentResolver, HandlerMethodReturnValueHandler, HttpMessageConverter 인터페이스를 모두 제공하기 때문에 확장해서 WebMvcConfigurer를 상속받은 설정 클래스에 빈으로 등록해주면 된다.
-> 잘 사용하지는 않음


Http 요청 시 HttpEntity 활용

나는 매번 @ReqeustBody로 Http 요청 바디를 받거나 헤더가 필요한 @RequestHeader , 헤더의 쿠키가 필요하면 @CookieValue 이런 애너테이션을 활용했다.

그래서 요청 시 HttpEntity는 어떤 식으로 활용하지.. ? 라는 생각이 들어서 간단한 예시만 넣어놓겠다.

@PostMapping("/users")
public ResponseEntity<String> saveUser(HttpEntity<User> httpEntity) {
    User user = httpEntity.getBody(); // JSON body → User 객체
    HttpHeaders headers = httpEntity.getHeaders(); // 요청 헤더
    System.out.println("Header = " + headers);
    System.out.println("Body = " + user);

    return ResponseEntity.ok("saved");
}

HttpEntity는 HTTP 바디, 헤더를 포함하는 객체다.

참고로 내가 HTTP 응답 시 주로 사용하는 ResponseEntityHttpEntity의 자식 클래스이며, 요청에는 RequestEntity가 있다.
두 클래스 모두 요청/응답의 헤더와 바디를 포함하며, RequestEntity는 추가로 HTTP 메서드와 URI를, ResponseEntity는 HTTP 상태 코드를 포함한다.

0개의 댓글