[Spring] HttpMessageConverter (1)

이연우·2025년 8월 18일

TIL

목록 보기
74/100

🌐 HttpMessageConverter란?

  • HTTP 메시지 바디 ↔ 자바 객체/문자열/바이트 변환기
    (ViewResolver 대신 바디 직접 입출력할 때 동작)
  • RequestBody/@ResponseBody바디 입출력ViewResolver 대신 동작하며,
    타입미디어 타입을 보고 적절한 컨버터가 read()/write()를 수행함

🎯 왜 필요할까? (SSR vs CSR)

  • SSR: @Controller + View 템플릿 → 서버가 HTML 렌더링 (ViewResolver 경로)
  • CSR/HTTP API: @RestController(+ @ResponseBody) → 서버가 데이터(JSON/TEXT/바이너리)를 메시지 바디에 직접 실어 보냄 (MessageConverter 경로)
    → 실무에선 두 방식을 혼합하는 경우가 많음

🕰️ 언제 동작할까?

  • 요청 바디 읽기: @RequestBody, HttpEntity<>, RequestEntity<>
  • 응답 바디 쓰기: @ResponseBody, ResponseEntity<>, @RestController
  • 요청과 응답 모두에서 HttpMessageConverter가 관여함

🔍 내부 동작(선택 로직)

  • 컨트롤러 메서드에 도달할 때/반환할 때,
    스프링은 등록된 컨버터 리스트를 앞에서부터 훑음
요청(Request)  : canRead(파라미터 타입, 요청 Content-Type) == true ? read() : 다음 컨버터
응답(Response) : canWrite(반환 타입, 요청 Accept/produces) == true ? write() : 다음 컨버터

canRead(Class<?> clazz, @Nullable MediaType contentType)
canWrite(Class<?> clazz, @Nullable MediaType accept)
read(Class<? extends T> clazz, HttpInputMessage input)
write(T body, @Nullable MediaType contentType, HttpOutputMessage output)

  • consumes/produces를 컨트롤러에 명시하면
    선택이 명확해지고 406/415류 오류를 줄일 수 있음

🧰 대표 컨버터 3개 & 우선순위

컨버터대상 타입주 미디어 타입기본 응답(Content-Type)사용 예
🟦 ByteArrayHttpMessageConverterbyte[]*/*application/octet-stream이미지/파일/바이너리
🟩 StringHttpMessageConverterString*/*text/plain단순 텍스트
🟧 MappingJackson2HttpMessageConverterObject/Mapapplication/jsonapplication/jsonDTO ↔ JSON (Jackson 필요)

리스트 앞쪽일수록 우선됨
→ 커스터마이징 시 0번 인덱스로 추가하면 최우선


👨‍🍳 최소 예제 코드

// DTO 예시
@Data
@AllArgsConstructor @NoArgsConstructor
public class DataDto {
  private String key;
}

@Slf4j
@RestController
@RequestMapping("/mc")
public class MessageConverterController {

  // 1) byte[] <-> octet-stream
  @PostMapping("/bytes")
  public byte[] bytes(@RequestBody byte[] data) {
    log.info("bytes");
    return data; // 기본 응답: application/octet-stream
  }

  // 2) String <-> text/plain
  @PostMapping(value = "/string", consumes = "text/plain", produces = "text/plain")
  public String string(@RequestBody String data) {
    log.info("string");
    return data;
  }

  // 3) DTO <-> application/json
  @PostMapping(value = "/json", consumes = "application/json", produces = "application/json")
  public DataDto json(@RequestBody DataDto dto) {
    log.info("json");
    return dto;
  }
}

🎬 Postman/cURL 시나리오

✅ 시나리오 A: JSON → String (성공)

  • 요청 헤더: Content-Type: application/json

  • 엔드포인트: POST /mc/string
    (주의: consumes = text/plain인 경우 A는 415, B로 테스트 권장)

  • 권장 테스트
    POST /mc/json으로 DTO 왕복을 먼저 확인하고,
    String 엔드포인트는 text/plain으로 테스트!

# String 엔드포인트(성공): text/plain
curl -X POST http://localhost:8080/mc/string \
  -H "Content-Type: text/plain" \
  -d "hello"

✅ 시나리오 B: JSON → DTO (성공)

curl -X POST http://localhost:8080/mc/json \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -d '{"key":"value"}'
# 기대: {"key":"value"} 와 함께 200 OK (또는 지정한 상태)

❌ 시나리오 C: text/plain → DTO (실패 예시)

  • 요청 헤더: Content-Type: text/plain
  • 컨트롤러 파라미터: @RequestBody DataDto
  • 결과: 매칭되는 canRead가 없어 415 Unsupported Media Type
curl -X POST http://localhost:8080/mc/json \
  -H "Content-Type: text/plain" \
  -d '{"key":"value"}'
# 기대: 415 Unsupported Media Type

MappingJackson2HttpMessageConverter는 보통 application/json을 읽음
해결: Content-Typeapplication/json으로 보내거나, 별도 컨버터/설정 준비

⚖️ 헤더 협상 핵심(Content-Type vs Accept)

  • 요청 바디 포맷 선언Content-Type (ex. application/json, text/plain)
  • 응답 포맷 선호Accept (ex. application/json)
  • 컨트롤러에 consumes/produces를 명시하면 매핑 단계에서 미리 거름
  • 기본 규칙(명시 안 했을 때):
    - String 반환 → text/plain
    - Object 반환 → application/json (Jackson 컨버터가 등록되어 있을 때)

🧭 오류 코드로 원인 역추적

상태언제?전형적 원인해결 방향
400 Bad Request파싱 시도했지만 실패JSON 문법 오류, DTO 타입 불일치, 빈 바디요청 바디/DTO 검증, 로그 확인
415 Unsupported Media Type읽기 컨버터 없음Content-Type과 파라미터 타입 불일치올바른 Content-Type, consumes 명시
406 Not Acceptable쓰기 컨버터 없음Accept와 반환 타입 불일치올바른 Accept, produces 명시
500 Internal Server Error직렬화/구성 문제Jackson 의존성 누락, 순환참조, Lazy 로딩의존성/모듈 추가, @JsonIgnore, DTO 분리

🧠 요약 정리

항목요청(Request, @RequestBody)응답(Response, @ResponseBody)
동작 시점클라이언트 → 서버 (HTTP Body → 자바 객체)서버 → 클라이언트 (자바 객체 → HTTP Body)
핵심 메서드canRead()read()canWrite()write()
판단 기준파라미터 타입 + 요청 Content-Type반환 타입 + 요청 Accept / 컨트롤러 produces
대표 컨버터ByteArrayHttpMessageConverter (byte[])
StringHttpMessageConverter (String)
MappingJackson2HttpMessageConverter (JSON → DTO)
동일 컨버터로 역방향 적용
장점SQL·View 없이 데이터 직접 바인딩 가능객체를 다양한 포맷(JSON, TEXT, BINARY)으로 쉽게 변환
단점Content-Type 불일치 시 415 오류Accept 불일치 시 406 오류
자주 발생 오류400 (문법 오류/파싱 실패)
415 (지원 불가 미디어 타입)
406 (Not Acceptable)
500 (직렬화 오류)
보완 방법컨트롤러에 consumes 지정
DTO 검증 철저
컨트롤러에 produces 지정
Jackson 모듈/DTO 분리
확장 포인트커스텀 컨버터 등록 (extendMessageConverters)응답 포맷별 컨버터 우선순위 조정

0개의 댓글