Search code examples
spring-webfluxhttpresponsespring-webclient

How to get ClientResponse body as DataBuffer in WebClient.exchangeToMono Spring 5.3?


Before the deprecation of WebClient.exchange method I used to get ClientResponse body as Flux<DataBuffer> and manipulated it.

In Spring 5.3 the exchange() method is deprecated and I would like to change the implementation as recommended:

@deprecated since 5.3 due to the possibility to leak memory and/or connections; please, use {@link #exchangeToMono(Function)}, {@link #exchangeToFlux(Function)}; consider also using {@link #retrieve()} ...

Tried to do the same call in the lambda passed to exchangeToMono, but clientResponse.bodyToFlux(DataBuffer::class.java) always return an empty flux; other experiments (i.e. getting body as mono string) also could not help to get the body.

What is the standard way to get the ClientResponse body in Spring 5.3 ?

I am looking for a low-level body representation: something like "data buffer", "byte array" or "input stream"; to avoid any kind of parsing/deserialisation.

Before Spring 5.3:

webClient
    .method(GET)
    .uri("http://somewhere.com")
    .exchange()
    .flatMap { clientResponse ->
       val bodyRaw: Flux<DataBuffer> = clientResponse.bodyToFlux(DataBuffer::class.java) 
       // ^ body as expected
           
       // other operations
    }

After Spring 5.3

webClient
    .method(GET)
    .uri("http://somewhere.com")
    .exchangeToMono { clientResponse ->
       val bodyRaw: Flux<DataBuffer> = clientResponse.bodyToFlux(DataBuffer::class.java)
       // ^ always empty flux
           
       // other operations
    }

Solution

  • The new exchangeToMono and exchangeToFlux methods expect the body to be decoded inside the callback. Check this GitHub issue for details.

    Looking at your example, probably you could use retrieve, that is safer alternative, with bodyToFlux

    webClient
            .method(GET)
            .uri("http://somewhere.com")
            .retrieve()
            .bodyToFlux(DataBuffer.class)
    

    or toEntityFlux if you need access to response details like headers and status

    webClient
            .method(GET)
            .uri("http://somewhere.com")
            .retrieve()
            .toEntityFlux(DataBuffer.class)
    

    Handling errors

    Option 1. Use onErrorResume and handle WebClientResponseException

    webClient
            .method(GET)
            .uri("http://somewhere.com")
            .retrieve()
            .bodyToFlux(DataBuffer.class)
            .onErrorResume(WebClientResponseException.class, ex -> {
                if (ex.getStatusCode().equals(HttpStatus.NOT_FOUND)) {
                    // ignore 404 and return empty
                    return Mono.empty();
                }
    
                return Mono.error(ex);
            });
    

    Option 2. Use onStatus convenience method to get access to response.

    webClient
            .method(GET)
            .uri("http://somewhere.com")
            .retrieve()
            .onStatus(status -> status.equals(HttpStatus.NOT_FOUND), res -> {
                // ignore 404 and return empty
                return Mono.empty();
            })
            .bodyToFlux(DataBuffer.class)
    

    Both methods could be used to deserialize error response Getting the response body in error case with Spring WebClient