Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Decode content-encoding gzip using Spring WebClient

I am calling a web service using Spring WebClient (Spring 5.1.3). The service responds with content-type: application/json and content-encoding: gzip

ClientResponse.bodyToMono then fails with the error "JSON decoding error: Illegal character ((CTRL-CHAR, code 31))" which I assume is because the content has not been decoded before trying to parse the JSON.

Here is code snippet (simplified) of how I create the WebClient

HttpClient httpClient = HttpClient.create().secure(sslContextSpec -> sslContextSpec.sslContext(sslContext));
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();

I then use the WebClient to make the call:

webClient.get().uri(uri)
    .accept(MediaType.APPLICATION_JSON)
    .header(HttpHeaders.ACCEPT_ENCODING, "gzip")
    .exchange()

The HTTP request has 2 headers:

Accept: application/json
Accept-Encoding: gzip

The response has the following headers:

set-cookie: xxx
content-type: application/json; charset=utf-8
content-length: 1175
content-encoding: gzip
cache-control: no-store, no-cache

By doing the following I am able to manually decode the GZIP content and get valid JSON from the result

webClient.get().uri(uri)
        .accept(MediaType.APPLICATION_JSON)
        .header("accept-encoding", "gzip")
        .exchange()
        .flatMap(encodedResponse -> encodedResponse.body((inputMessage, context) ->
                inputMessage.getBody().flatMap(dataBuffer -> {
                    ClientResponse.Builder decodedResponse = ClientResponse.from(encodedResponse);
                    try {
                        GZIPInputStream gz = new GZIPInputStream(dataBuffer.asInputStream());
                        decodedResponse.body(new String(gz.readAllBytes()));
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                    decodedResponse.headers(headers -> {
                        headers.remove("content-encoding");
                    });
                    return Mono.just(decodedResponse.build());
                }).flatMap(clientResponse -> clientResponse.bodyToMono(Map.class))
like image 744
athom Avatar asked Dec 27 '18 04:12

athom


Video Answer


1 Answers

This feature is supported natively by the reactor netty client.

You should create HttpClient like this:

HttpClient httpClient = HttpClient.create()
             .secure(sslContextSpec -> sslContextSpec.sslContext(sslContext))
             .compress(true);

And then there's no need to add the accept encoding request header since it's done for you.

Note that this bit is done by the connector itself when you don't provide a custom HttpClient instance.

like image 81
Brian Clozel Avatar answered Oct 18 '22 02:10

Brian Clozel