Could not extract response no suitable HttpMessageConverter found for response type...
in Debugging
SpringMVC, OpenFeign 사용 중 발생
🚨 문제
회사 서비스를 확장하던 중 발생한 이슈이다.
타 회사의 API에 우리 서비스를 연동해야 하는 상황이었다.
집에 오자마자 데모 프로젝트를 두개 만들어 해당 상황을 재현해봤는데, 재현이 아주 잘 된다.
2021-12-20 19:39:25.637 ERROR 3936 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is feign.codec.DecodeException: Could not extract response: no suitable HttpMessageConverter found for response type [class io.github.shirohoo.client.api.ApiController$Response] and content type [text/html]] with root cause
org.springframework.web.client.UnknownContentTypeException: Could not extract response: no suitable HttpMessageConverter found for response type [class io.github.shirohoo.client.api.ApiController$Response] and content type [text/html]
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:126)
🚧 원인
SpringMVC
,OpenFeign
사용 중 발생- 원인: API 서버에서 응답하는 실 데이터는
application/json
- 하지만
Content-Type
은text/html
😒 - 즉, 웹 표준을 지키지 않는 응답으로 인한 문제
- 하지만
🚧 재현
재현용 데모 프로젝트를 두개 만들었다.
응답 서버에서는 객체를 JSON
으로 직렬화해 응답하면서 Content-Type=text/html
으로 헤더를 설정해볼 것이다.
Spring MVC
,OpenFeign
프로젝트 = 요청 서버Spring MVC
프로젝트 = 응답 서버
우선 표준을 제대로 지키지 않는 응답서버를 간단하게 하나 구현한다.
@Slf4j
@RestController
public class FakeResponseController {
@PostMapping
public ResponseEntity<Response> post(@RequestBody Request request) {
log.info("request={}", request);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.setContentType(MediaType.TEXT_HTML);
Response httpBody = Response.builder()
.responseCode(HttpStatus.OK.value())
.responseMessage(HttpStatus.OK.getReasonPhrase())
.body(request)
.build();
log.info("httpBody={}", httpBody);
return ResponseEntity.status(HttpStatus.OK)
.headers(httpHeaders)
.body(httpBody);
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Request {
private String phoneNumber;
private long amount;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Response<T> {
private int responseCode;
private String responseMessage;
private T body;
}
}
그리고 OpenFeign
을 이용해 응답서버에 HTTP 요청을 보내야 하므로 이를 구현한다.
@Slf4j
@RestController
@RequiredArgsConstructor
public class RequestController {
private final ApiServerClient client;
@GetMapping
public Response get() {
Request request = new Request("010-1234-5678", 50_000);
Response response = client.post(request);
log.info("response={}", response);
return response;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Request {
private String phoneNumber;
private long amount;
}
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class Response {
private int responseCode;
private String responseMessage;
private Request body;
}
}
그리고 두 서버를 모두 기동하고 요청을 보냈다.
버그 상황이 재현된 모습
✅ 해결
stack trace
만 보자면 Content-Type=text/html
을 처리해주는 HttpMessageConverter
가 없는 것 같다.
이를 직접 구현할까 고민하다, 앞으로 수십개 벤더의 API를 다 연동해야 하는데, 별별 상황이 더 나올 것 같아 gson
의존성을 추가해서 해결해보기로 결정했다.
gson
을 프로젝트에 추가하고 Content-Type=text/html
이여도 처리하도록 확장해줄 것이다.
// file: 'build.gradle'
dependencies {
...
implementation 'com.google.code.gson:gson:2.8.9' // 현 시점 최신 버전을 추가
...
}
@Component
public static class ExpandGsonHttpMessageConverter extends GsonHttpMessageConverter {
public ExpandGsonHttpMessageConverter() {
List<MediaType> types = Arrays.asList(
new MediaType(MediaType.TEXT_HTML, DEFAULT_CHARSET),
new MediaType(MediaType.TEXT_PLAIN, DEFAULT_CHARSET),
new MediaType(MediaType.TEXT_XML, DEFAULT_CHARSET)
);
super.setSupportedMediaTypes(types);
}
}
그리고 두 서버를 모두 재기동한 후 다시 요청을 보내보았다.
응답 서버의 로그
2021-12-20 20:02:55.853 INFO 30796 --- [nio-8081-exec-1] i.g.s.api.api.FakeResponseController : request=FakeResponseController.Request(phoneNumber=010-1234-5678, amount=50000)
2021-12-20 20:02:55.854 INFO 30796 --- [nio-8081-exec-1] i.g.s.api.api.FakeResponseController : httpBody=FakeResponseController.Response(responseCode=200, responseMessage=OK, body=FakeResponseController.Request(phoneNumber=010-1234-5678, amount=50000))
요청 서버의 로그
2021-12-20 20:02:55.870 INFO 9108 --- [nio-8080-exec-1] i.g.s.client.api.RequestController : response=RequestController.Response(responseCode=200, responseMessage=OK, body=RequestController.Request(phoneNumber=010-1234-5678, amount=50000))
문제없이 아주 잘 된다.
내일 출근하면 적용해야겠다.