What's the correct way to get the response body from a WebClient in an error case?
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.
Related videos on Youtube
BetaRide
Developer, maker, youtuber. Visit my youtube channel: https://www.youtube.com/channel/UCN8wBEouFtaAyIjjIH2LjJQ
Updated on June 04, 2022Comments
-
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 over 4 yearsThanks for the answer.May I know how to throw an exception in case if its error.
-
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 about 4 yearsyou use the
onStatus
method as shown in the official documentation. docs.spring.io/spring/docs/current/spring-framework-reference/… 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 over 2 yearsIn 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 over 2 yearsExchangeToMono(response -> …) gives you a response that you can do whatever you want with