[spring] webClient error 처리
기존코드
WebClient.create().get()
.uri("http://~~~")
.retrieve()
.bodyToMono(CustomResponse::class.java)
.onErrorMap { e ->
log.error(e.message)
e // return e
}
.map {
// return
}
CustomResponse 타입의 body를 가져와서 처리한다.
에러가 발생하는 경우(4xx, 5xx) onErrorMap이 에러를 캐치해서 log.error로 해당에러를 찍는다.
WebClient.create().get()
.uri("http://~~~~")
.exchange()
.flatMap { response ->
// get header
val header = response.headers().header("headerName")[0]
if (response.statusCode().isError) {
// error. example -> throw WebClientResponseException
throw WebClientResponseException(
response.statusCode().value(),
response.statusCode().reasonPhrase,
response.headers().asHttpHeaders(),
null, null)
}
response.bodyToMono(CustomResponse::class.java).map{
// ~~~
}
}
.onErrorMap { e ->
// error 처리
log.error(e.message)
e // return e
}
응답값의 body뿐만 아니라 header도 필요해서 retrieve에서 exchage로 변경했다.
Error가 발생한지는 response.statusCode를 확인해서 error인 경우
WebClientResponseException을 명시적으로 만들어줘서 던졌다.
이렇게 던지면 onErrorMap이 다시 에러를 인식할 수 있을 것이다.
명시적으로 에러를 던져주지 않으면 4xx, 5xx 에러인 경우 이를 인식할 수 없다. (4xx, 5xx 응답으로 인해 에러가 발생되지는 않는다.)
따라서 flatMap에 있는 로직을 계속 탈 것이고 결국 bodyToMono(CustomerResponse::class.java)에서 UnsupportedMediaTypeException: not supported for bodyType=CustomResponse 에러가 발생한다.
body가 CustomResponse 타입이 아니기 때문에 발생하는 에러이다.
위에는 문서에서 가져온 retrieve에 대한 설명이다.
Retrieve란 exchange 후 response body를 디코딩한다.
내 생각에는 response body를 디코딩할 때 문제가 생기면 WebClientResponseException 에러가 발생해서 그걸 onErrorMap에서 잡아내는 듯 하다.
+ 19.05.10
응답값이 body 타입에 맞지 않아서 WebClientResponseException이 발생한거라고 생각했다.
그렇다면 String을 응답 타입으로 한다면 에러가 발생하지 않을 것이라고 생각하고 실제로 돌려봤다.
(통신하는 곳은 무조건 400에러를 뱉게하고 테스트)
결과는 기존과 똑같이 4xx, 5xx 에러인 경우 에러를 뱉어냈다.
그래서 retrieve 코드를 타고 올라가보니 (org.springframework:spring-webflux:5.1.3.RELEASE 기준.)
private static class DefaultResponseSpec implements ResponseSpec {
// HttpStatus가 에러인경우(4xx, 5xx) 에러 핸들러 등록
private static final StatusHandler DEFAULT_STATUS_HANDLER =
new StatusHandler(HttpStatus::isError, DefaultResponseSpec::createResponseException);
private final Mono<ClientResponse> responseMono;
private final List<StatusHandler> statusHandlers = new ArrayList<>(1);
DefaultResponseSpec(Mono<ClientResponse> responseMono) {
this.responseMono = responseMono;
this.statusHandlers.add(DEFAULT_STATUS_HANDLER);
}
위와 같은 코드가 있었다.
코드를 보면 HttpStatus가 error(4xx, 5xx)인 경우 에러 핸들러가 등록되어있다.
그래서 retrieve에서는 통신 결과가 에러인 경우 스프링에서 에러를 뱉어준다.
cf) createResponseException 함수
응답이 200이고 응답 body에 대한 처리만 하고 싶은 경우 retrieve.
이 외에 응답 코드가 4xx, 5xx 등 특정 코드에 따라 다른 결과를 주고 싶거나 응답 헤더를 사용하고 싶은 경우는 exchange를 사용하자.