Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Retrofit: how to parse GZIP'd response without Content-Encoding: gzip header

I'm trying to process a server response which is GZIP'd. The response comes with a header

Content-Type: application/x-gzip

but does not have header

Content-Encoding: gzip

If I add that header using a proxy, the response gets parsed just fine. I don't have any control over the server, so I can't add the header.

Can I force Retrofit to treat it as GZIP content? Is there a better way? The URL for the server is: http://crowdtorch.cms.s3.amazonaws.com/4474/Updates/update-1.xml

like image 362
itsymbal Avatar asked May 19 '16 20:05

itsymbal


2 Answers

I figured it out. The idea is to add a custom interceptor which will take the not-yet-unzipped response, and unzip it 'manually' - do the same thing that OkHttp would do automatically based on Content-Encoding header, but without requiring that header.

is like dis:

    OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
            .addInterceptor(new UnzippingInterceptor());
    OkHttpClient client = clientBuilder.build();

And the Interceptor is like dis:

private class UnzippingInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Response response = chain.proceed(chain.request());
        return unzip(response);
    }
}

And the unzip function is like dis:

    // copied from okhttp3.internal.http.HttpEngine (because is private)
private Response unzip(final Response response) throws IOException {

    if (response.body() == null) {
        return response;
    }

    GzipSource responseBody = new GzipSource(response.body().source());
    Headers strippedHeaders = response.headers().newBuilder()
            .removeAll("Content-Encoding")
            .removeAll("Content-Length")
            .build();
    return response.newBuilder()
            .headers(strippedHeaders)
            .body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)))
            .build();
}
like image 153
itsymbal Avatar answered Oct 20 '22 01:10

itsymbal


There is a better way than reinventing the wheel. Just add the Content-Encoding header yourself.

.addNetworkInterceptor((Interceptor.Chain chain) -> {
    Request req = chain.request();
    Headers.Builder headersBuilder = req.headers().newBuilder();

    String credential = Credentials.basic(...);
    headersBuilder.set("Authorization", credential);

    Response res = chain.proceed(req.newBuilder().headers(headersBuilder.build()).build());

    return res.newBuilder()
        .header("Content-Encoding", "gzip")
        .header("Content-Type", ""application/json")
        .build();
})

In fact, your code is a classic example of the evils of using internal code (like com.sun packages from the JDK). RealResponseBody doesn't have that constructor anymore.

like image 3
Abhijit Sarkar Avatar answered Oct 20 '22 01:10

Abhijit Sarkar