스프링 공부 (인프런 김영한 선생님)/스프링 MVC 1편

[스프링 웹 MVC 1편] 26. HTTP 메세지 컨버터

ProgYun. 2023. 6. 4. 16:03

@RequestBody를 컨트롤러 메서드단에 적용하면 HTTP 메세지 바디에 그대로 String 문자열이나,

ResponseEntity의 인자로 객체를 반환하면 JSON으로 문자열을 반환하면 OK와 같은 문자열을 그대로 입력할 수 있다.

 

응답으로 객체/문자열을 처리하는 방법은 이전 시간에 논의한 내용을 참고하자

 

https://progyun.tistory.com/209

 

[스프링 웹 MVC 1편] 25. HTTP 응답 - HTTP API, 메세지 바디에 직접 입력

package hello.springmvc.basic.response; import hello.springmvc.basic.HelloData; import java.io.IOException; import javax.servlet.http.HttpServletResponse; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframew

progyun.tistory.com

이때 viewResolver 대신 HttpMessageConverter가 동작하게 되는데 이 인터페이스의 구체 클래스로

1)  기본 문자처리를 위한 StringHttpMessageConverter

2)  기본 객체처리를 위한 MappingJackson2HttpMessageConverter

3)  이외에도 바이트 코드 등 기타 HttpMessageConverter가 등록되어 있다.

 

응답의 경우 클라이언트의 HTTP Accept Header와 서버 컨트롤러의 반환 타입 정보 둘을 조합해서 HttpMessageConverter가 선택됨.

 

스프링 MVC는 다음 경우에 HTTP 메세지 컨버터를 적용함

-> HTTP 요청 : @RequestBody, HttpEntity(RequestEntity)

-> HTTP 응답 : @ResponseBody, HttpEntity(ResponseEntity)

 

/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.springframework.http.converter;

import java.io.IOException;
import java.util.Collections;
import java.util.List;

import org.springframework.http.HttpInputMessage;
import org.springframework.http.HttpOutputMessage;
import org.springframework.http.MediaType;
import org.springframework.lang.Nullable;

/**
 * Strategy interface for converting from and to HTTP requests and responses.
 *
 * @author Arjen Poutsma
 * @author Juergen Hoeller
 * @author Rossen Stoyanchev
 * @since 3.0
 * @param <T> the converted object type
 */
public interface HttpMessageConverter<T> {

 /**
  * Indicates whether the given class can be read by this converter.
  * @param clazz the class to test for readability
  * @param mediaType the media type to read (can be {@code null} if not specified);
  * typically the value of a {@code Content-Type} header.
  * @return {@code true} if readable; {@code false} otherwise
  */
 boolean canRead(Class<?> clazz, @Nullable MediaType mediaType);

 /**
  * Indicates whether the given class can be written by this converter.
  * @param clazz the class to test for writability
  * @param mediaType the media type to write (can be {@code null} if not specified);
  * typically the value of an {@code Accept} header.
  * @return {@code true} if writable; {@code false} otherwise
  */
 boolean canWrite(Class<?> clazz, @Nullable MediaType mediaType);

 /**
  * Return the list of media types supported by this converter. The list may
  * not apply to every possible target element type and calls to this method
  * should typically be guarded via {@link #canWrite(Class, MediaType)
  * canWrite(clazz, null}. The list may also exclude MIME types supported
  * only for a specific class. Alternatively, use
  * {@link #getSupportedMediaTypes(Class)} for a more precise list.
  * @return the list of supported media types
  */
 List<MediaType> getSupportedMediaTypes();

 /**
  * Return the list of media types supported by this converter for the given
  * class. The list may differ from {@link #getSupportedMediaTypes()} if the
  * converter does not support the given Class or if it supports it only for
  * a subset of media types.
  * @param clazz the type of class to check
  * @return the list of media types supported for the given class
  * @since 5.3.4
  */
 default List<MediaType> getSupportedMediaTypes(Class<?> clazz) {
  return (canRead(clazz, null) || canWrite(clazz, null) ?
    getSupportedMediaTypes() : Collections.emptyList());
 }

 /**
  * Read an object of the given type from the given input message, and returns it.
  * @param clazz the type of object to return. This type must have previously been passed to the
  * {@link #canRead canRead} method of this interface, which must have returned {@code true}.
  * @param inputMessage the HTTP input message to read from
  * @return the converted object
  * @throws IOException in case of I/O errors
  * @throws HttpMessageNotReadableException in case of conversion errors
  */
 T read(Class<? extends T> clazz, HttpInputMessage inputMessage)
   throws IOException, HttpMessageNotReadableException;

 /**
  * Write a given object to the given output message.
  * @param t the object to write to the output message. The type of this object must have previously been
  * passed to the {@link #canWrite canWrite} method of this interface, which must have returned {@code true}.
  * @param contentType the content type to use when writing. May be {@code null} to indicate that the
  * default content type of the converter must be used. If not {@code null}, this media type must have
  * previously been passed to the {@link #canWrite canWrite} method of this interface, which must have
  * returned {@code true}.
  * @param outputMessage the message to write to
  * @throws IOException in case of I/O errors
  * @throws HttpMessageNotWritableException in case of conversion errors
  */
 void write(T t, @Nullable MediaType contentType, HttpOutputMessage outputMessage)
   throws IOException, HttpMessageNotWritableException;

}

 

HTTP 메세지 컨버터는 HTTP 요청, HTTP 응답 모두에 의해 사용됨

-> canRead(), canWrite()를 통해 메세지 컨버터가 해당 클래스나 미디어타입(consumes, produces)를 지원하는지 체크

-> read(), write() 메세지 컨버터를 통해서 메세지를 읽고 쓰는 기능

 


스프링 부트 기본 메세지 컨버터

0 - ByteArrayHttpMessageConverter

1 - StringHttpMessageConverter

2 = MappingJackson2HttpMessageConverter : application/json

 

- 스프링 부트의 다양한 메세지 컨버터 중 일부를 서술한 것이다

대상 클래스 타입과 미디어타입(content-type) 둘을 체크해서 사용 여부를 결정하게 된다.

만족하지 않는 경우 다음 메세지 컨버터로 우선순위가 넘어간다.

 

1) ByteArrayMessageConverter : byte[] 데이터를 처리한다.

- 클래스 타입 String, 미디어 타입 : */*

요청 예시) @RequestBody byte[] java

응답 예시) @ResponseBody return byte[] / 미디어 타입 : application/octet-stream

 

2) StringHttpMessageConverter : String 문자로 데이터를 처리한다

- 클래스 타입 : String, 미디어타입 : */*

요청 예시) @RequestBody String data

응답 예시) @ResponseBody return "ok" / 미디어 타입 text-plain

 

3) MappingJackson2HttpMessageConverter: application/json

- 클래스 타입: 객체 또는 HashMap, 미디어 타입: application/json 관련

요청 예시 : @RequestBody HelloData data

응답 예시 : @ResponseBody return helloData / 미디어 타입 application/json 관련

 

헷갈렸던 내용이 요청 응답 어떻게 구분할 것인가였는데

이제 보니 요청 데이터 관련의 경우 메서드의 파라미터 단으로 HttpEntity나 @RequestBody가 들어가고

응답 데이터 관련한 경우는 HttpEntity나 @ResponseBody로 값이 반환된다.

 


HTTP 요청 데이터 읽기

- HTTP 요청이 오고, 컨트롤러에서 @RequestBody HttpEntity 파라미터를 사용한다.

- 메세지 컨버터가 메세지를 읽을 수 있는지 확인하기 위해 canRead()를 호출한다.

-> 대상 클래스 타입을 지원하는가? (예) @RequestBody의 대상 클래스가 byte[]인지 String인지 HelloData인지

-> HTTP 요청의 Content-Type 미디어 타입을 지원하는가 (예) text/plain, application/json, */*

- canRead() 조건을 만족하는 read()를 호출하고 객체 생성하고, 반환한다.

 

HTTP 응답 데이터 생성

- 컨트롤러에서 @ResponseBody, HttpEntity로 값이 반환된다

- 메세지 컨버터가 메세지를 쓸 수 있는지 확인하기 위해, canWrite()를 호출한다.

-> 대상 클래스 타입을 지원하는가? (예) return의 대상 클래스가 byte[]인지 String인지 HelloData인지

-> HTTP 요청의 Accept 미디어 타입을 지원하는가 / 더 자세히는 @RequestMapping의 produces

= 클라이언트가 읽을 수 있는 자료의 출력이 필요하기 때문이다

(예) text/plain, application/json, */*

- canWrite() 조건을 만족하는 write()를 호출해서 HTTP 응답 메세지 바디에 데이터를 생성한다.

 

==> 만약 세가지가 다 탈락하게 되면 에러가 발생한다.