What's the correct way to get the response body from a WebClient in an error case?

10,926

both solutions are ugly and wrong. You should almost never subscribe in the middle of a reactive pipeline. The subscriber is usually the calling client, not your own application.

    public Mono<String> log(ProtocolLine protocolLine) {
    return webClient.post()
            .uri("/log")
            .body(BodyInserters.fromObject(protocolLine))
            .exchange()
            .flatMap(clientResponse -> clientResponse.bodyToMono(String.class)
                .doOnSuccess(body -> {
                    if (clientResponse.statusCode().isError()) {
                        log.error("HttpStatusCode = {}", clientResponse.statusCode());
                        log.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
                        log.error("ResponseBody = {}", body);
                    }
            }));
}

Here you can see the way of thinking. We always take our clientResponse and map its body to a string. We then doOnSuccess when this Mono is consumed by the subscriber (our calling client) and check the status code if there is an error and if that is the case we log.

The doOnSuccess method returns void so it doesn't "consume" the mono or anything, it just triggers something when this Mono says it "has something in itself", when it's "done" so to speek.

This can be used with Flux the same way.

Share:
10,926

Related videos on Youtube

BetaRide
Author by

BetaRide

Developer, maker, youtuber. Visit my youtube channel: https://www.youtube.com/channel/UCN8wBEouFtaAyIjjIH2LjJQ

Updated on June 04, 2022

Comments

  • BetaRide
    BetaRide almost 2 years

    I'm new to WebClient and reactive programming. I want to get the response body from a request. In case of an error the http-code, headers and body must be logged, but the body should still be returned.

    After lots of digging and googling I found two solutions. But both look over complicated to me. Is there a simpler solution?

    Staying with a Mono I found this solution:

    public Mono<String> log(ProtocolLine protocolLine) {
        return webClient.post()
                .uri("/log")
                .body(BodyInserters.fromObject(protocolLine))
                .exchange()
                .flatMap(clientResponse -> {
                    Mono<String> stringMono = clientResponse.bodyToMono(String.class);
                    CompletableFuture<String> stringCompleteFuture = new CompletableFuture<String>();
                    Mono<String> bodyCompletedMono = Mono.fromFuture(stringCompleteFuture);
                    if (clientResponse.statusCode().isError()) {
                        stringMono.subscribe(bodyString -> {
                            LOGGER.error("HttpStatusCode = {}", clientResponse.statusCode());
                            LOGGER.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
                            LOGGER.error("ResponseBody = {}", bodyString);
                            stringCompleteFuture.complete(bodyString);
                        });
                    }
    
                    return bodyCompletedMono;
                });
    }
    

    Based on Flux it takes less code. But I think I should not use Flux if I know that there will be only one result.

    public Flux<String> log(ProtocolLine protocolLine) {
        return webClient.post()
                .uri("/log")
                .body(BodyInserters.fromObject(protocolLine))
                .exchange()
                .flux()
                .flatMap(clientResponse -> {
                    Flux<String> stringFlux = clientResponse.bodyToFlux(String.class).share();
                    if (clientResponse.statusCode().isError()) {
                        stringFlux.subscribe(bodyString -> {
                            LOGGER.error("HttpStatusCode = {}", clientResponse.statusCode());
                            LOGGER.error("HttpHeaders = {}", clientResponse.headers().asHttpHeaders());
                            LOGGER.error("ResponseBody = {}", bodyString);
                        });
                    }
    
                    return stringFlux;
                });
    }
    
  • Spartan
    Spartan over 4 years
    Thanks for the answer.May I know how to throw an exception in case if its error.
  • npeder
    npeder about 4 years
    @ThomasAndolf How would you do this if using retrieve() instead of exchange()? And the successful response and error response contained different types (success would return SuccessResponse.class and error would return ErrorResponse.class, both serialized into json)?
  • Toerktumlare
    Toerktumlare about 4 years
    you use the onStatus method as shown in the official documentation. docs.spring.io/spring/docs/current/spring-framework-referenc‌​e/… And then what you are asking for is that you want to return 2 different types of objects from the same function. You cant return (for example) a String or an Int from the same function. If you read the reactor documentation, you return a mono error (containing say en exception or something) and then later in the chain you have a .doOnError.
  • Julien Busset
    Julien Busset over 2 years
    In Spring 5, exchange() is deprecated: is there another way to get the body in case of an error? With retrieve() or exchangeToMono(), I just can’t get it.
  • Toerktumlare
    Toerktumlare over 2 years
    ExchangeToMono(response -> …) gives you a response that you can do whatever you want with