How to get response body when testing the status code in WebFlux WebClient?

CoryO picture CoryO · Oct 15, 2017 · Viewed 52.7k times · Source

How do you retrieve the response body when trying to throw an exception based on the returned status code? For instance, lets say I want to throw an exception and reject HTTP 201.

client.post().exchange().doOnSuccess(response -> {
    if (response.statusCode().value() == 201) {
        throw new RuntimeException();
    }
}

How can I populate the exception with the response's body so I can throw a detailed WebClientResponseException?

Should I be using a different method to test the response status code?

edit: I am trying to duplicate the following functionality while using exchange() instead.

client.get()
    .retrieve()
    .onStatus(s -> !HttpStatus.CREATED.equals(s),
        MyClass::createResponseException);

//MyClass
public static Mono<WebClientResponseException> createResponseException(ClientResponse response) {
    return response.body(BodyExtractors.toDataBuffers())
            .reduce(DataBuffer::write)
            .map(dataBuffer -> {
                byte[] bytes = new byte[dataBuffer.readableByteCount()];
                dataBuffer.read(bytes);
                DataBufferUtils.release(dataBuffer);
                return bytes;
            })
            .defaultIfEmpty(new byte[0])
            .map(bodyBytes -> {
                String msg = String.format("ClientResponse has erroneous status code: %d %s", response.statusCode().value(),
                        response.statusCode().getReasonPhrase());
                Charset charset = response.headers().contentType()
                        .map(MimeType::getCharset)
                        .orElse(StandardCharsets.ISO_8859_1);
                return new WebClientResponseException(msg,
                        response.statusCode().value(),
                        response.statusCode().getReasonPhrase(),
                        response.headers().asHttpHeaders(),
                        bodyBytes,
                        charset
                        );
            });
}

Answer

ROCKY picture ROCKY · Feb 26, 2018

You could achieve like this by having a custom ExchangeFilterFunction and then hooking this up with WebClient.Builder before you build WebClient.

public static ExchangeFilterFunction errorHandlingFilter() {
        return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
            if(clientResponse.statusCode()!=null && (clientResponse.statusCode().is5xxServerError() || clientResponse.statusCode().is4xxClientError()) ) {
                 return clientResponse.bodyToMono(String.class)
                         .flatMap(errorBody -> {
                             return Mono.error(new CustomWebClientResponseException(errorBody,clientResponse.statusCode()));
                             });
            }else {
                return Mono.just(clientResponse);
            }
        });
    }

You can use the above like this:

WebClient.builder()
                .clientConnector(new ReactorClientHttpConnector(clientOptions))
                .defaultHeader(HttpHeaders.USER_AGENT, "Application")
                .filter(WebClientUtil.errorHandlingFilter())
                .baseUrl("https://httpbin.org/")
                .build()
                .post()
                .uri("/post")
                .body(BodyInserters.fromObject(customObjectReference) )
                .exchange()
                .flatMap(response -> response.toEntity(String.class) );

So any 4XX or 5XX HttpResponse will actually throw CustomWebClientResponseException and you can configure some global exception handler and do what you like to with this. Atleast using ExchangeFilterFunction you can have global place to handle things like this or add custom headers and stuff too.