3

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;
            });
}
BetaRide
  • 16,207
  • 29
  • 99
  • 177

1 Answers1

3

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.

Toerktumlare
  • 12,548
  • 3
  • 35
  • 54
  • Thanks for the answer.May I know how to throw an exception in case if its error. – Spartan Dec 24 '19 at 06:30
  • @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)? – npeder Feb 20 '20 at 10:38
  • you use the `onStatus` method as shown in the official documentation. https://docs.spring.io/spring/docs/current/spring-framework-reference/web-reactive.html#webflux-client-retrieve 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`. – Toerktumlare Feb 20 '20 at 14:05
  • 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. – Julien Busset Dec 06 '21 at 13:50
  • ExchangeToMono(response -> …) gives you a response that you can do whatever you want with – Toerktumlare Dec 06 '21 at 18:12