Search code examples
springspring-bootspring-mvcspring-webfluxspring-webclient

WebClient filter to transform the response body


I'm using Spring Declarative HTTP Clients and I'm having a hard time being siloed with WebClient. Seems that it still has very poor documentation and few examples over the internet.

I'm trying to transform the response body with a filter to remove some boilerplate json from it. Here's what I tried:

    private ExchangeFilterFunction responseUnwrapper() {
    return ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
        if (clientResponse.statusCode().is2xxSuccessful()) {
            return clientResponse
                    .bodyToMono(DataDTO.class)
                    .map(DataDTO::data)
                    .flatMap(mappedResponse -> Mono.just(ClientResponse.create(clientResponse.statusCode())
                            .headers(httpResponse -> clientResponse.headers().asHttpHeaders())
                            .body(mappedResponse)
                            .build()));
        } else {
            return Mono.just(clientResponse);
        }
    });
}

So basically the server response wraps the body in a "data": {} attribute which I'm trying to remove here. The above code doesn't compile because the body function of ClientReponse.Builder expects one of the following:

    Builder body(Function<Flux<DataBuffer>, Flux<DataBuffer>> transformer);

    Builder body(Flux<DataBuffer> body);

    Builder body(String body);

It is not clear to me how to use those, and if I just do .body(mappedResponse.toString()) I get an IllegalStateException: Timeout on blocking read for 5000000000 NANOSECONDS

So how do I properly transform a response body using WebClient?

EDIT: this is DataDTO code

public record DataDTO<T>(T data) {}

Solution

  • You have to serialize your data into a String a pass it to the Builder body(String body). Probably you want to build a json, however technically mappedResponse.toString() also fits. Your IllegalStateException is not related to that part of code. Looks like you are calling block() somewhere on a webclient's response Mono.

    By the way, you don't need flatMap here, map is enough:

                ExchangeFilterFunction.ofResponseProcessor(clientResponse -> {
                    if (clientResponse.statusCode().is2xxSuccessful()) {
                        return clientResponse
                                .bodyToMono(DataDTO.class)
                                .map(DataDTO::data)
                                .map(dtoData -> ClientResponse.create(clientResponse.statusCode())
                                        .headers(httpResponse -> clientResponse.headers().asHttpHeaders())
                                        .body(dtoData.toString()) // probably Jackson's objectMapper.writeValueAsString()
                                        .build());
                    } else {
                        return Mono.just(clientResponse);
                    }
                });