[WebClient] Content type 'application/octet-stream' not supported for bodyType='' 에러
webClient를 사용중에 bodyToMono를 사용했더니 에러가 발생했다.
// 대략 이런 코드
fun convertErrorResponse(clientResponse: ClientResponse): Mono<ErrorResponse> {
return clientResponse.bodyToMono(ErrorResponse::class.java)
}
위의 코드처럼 사용하려고 했더니 발생한 에러문구
Content type 'application/octet-stream' not supported for bodyType='변환하려는 클래스'
webClient 요청시 Content-Type을 application/json으로 붙여서 보내라는 해결법도 있었는데, 난 이미 그렇게 처리하고 있어서 그게 이유는 아닌듯했고, 내가 클라이언트일 경우 위의 에러 발생 시 해결방법이 구글링으로 잘 나오지 않았다.(대부분 자신이 서버라서 Controller에서 ~~이렇게 받아라~ 라는 내용들)
확인해보니 응답을 주는 서버측에 application/json으로 요청 해도 Response의 Content-Type 정보없이 내려주고 있었다.
찬찬히 살펴보자면, webflux의 ClientResponse(인터페이스이다.)는 딱히 설정하지않으면 DefaultClientResponse로 동작하고 여기서 bodyToMono 메서드는 BodyExtractors를 사용해 Responsebody를 Mono로 변환한다.
BodyExtractors.toMono를 타고타고 들어가다보면 readWithMessageReaders 메서드를 타게 된다.
주석에 표시된 주목!!을 주목하라!
// spring-webflux-6.0.5
// BodyExtractors.java
private static <T, S extends Publisher<T>> S readWithMessageReaders(
ReactiveHttpInputMessage message, BodyExtractor.Context context, ResolvableType elementType,
Function<HttpMessageReader<T>, S> readerFunction,
Function<UnsupportedMediaTypeException, S> errorFunction,
Supplier<S> emptySupplier) {
if (VOID_TYPE.equals(elementType)) {
return emptySupplier.get();
}
// 아래 코드 주목!!
MediaType contentType = Optional.ofNullable(message.getHeaders().getContentType())
.orElse(MediaType.APPLICATION_OCTET_STREAM);
return context.messageReaders().stream()
.filter(reader -> reader.canRead(elementType, contentType))
.findFirst()
.map(BodyExtractors::<T>cast)
.map(readerFunction)
.orElseGet(() -> {
List<MediaType> mediaTypes = context.messageReaders().stream()
.flatMap(reader -> reader.getReadableMediaTypes(elementType).stream())
.toList();
return errorFunction.apply(
new UnsupportedMediaTypeException(contentType, mediaTypes, elementType));
});
}
response에 Content-Type이 없다면 .orElse()를 통해 Content-Type은 자동적으로 applcation/octet-stream(8비트 단위의 바이너리 데이터)으로 취급한다.
결론적으로 요청한 서버측에서는 Content-Type을 application/octet-stream으로 준적 없다. BodyExtractors에서 Content-Type이 비어있어 그렇게 판단했을뿐.
json 형태라는 걸 알 수 없으니 정해진 데이터 모델로 변환할 수 없어 에러가 난것으로 보인다.
요청하는 서버 담당자에게 말해서 수정되면 좋겠지만, 급한 건 내쪽이고 언제 수정될지 알 수 없는 상황에서는 이가 없으면 잇몸으로 해결해야한다.(더 깔끔한 방법이 있다면 알려주시면 감사합니다.)
Content-Type은 없더라도 정상적인 에러 응답을 내려주고 있을터라 다음과 같은 방식을 시도해봤다.
1. 바이트 코드를 String으로 변환한다.
2. String을 ObjectMapper를 이용해 변환하려고 했던 모델로 읽어낸다.
(ObjectMapper의 경우, Spring에서 기본으로 사용하고있는 ObjectMapper 빈을 주입받아 사용했다)
fun convertErrorResponse(clientResponse: ClientResponse): Mono<ErrorResponse> {
return clientResponse.bodyToMono(String::class.java).map {
objectMapper.readValue(it, ErrorResponse::class.java)
}
}
정상적으로 에러 응답 컨버팅 성공!