I have an Android app that talks to a Web API hosted in Azure to retrieve blobs of JSON - this works just fine out of the box using the following client code to initiate the connection:

private InputStream openUrlStream(String url) throws IOException {
    URLConnection connection = new URL(url).openConnection();
    return connection.getInputStream();
}

Now, because my JSON can get reasonably large I wanted to enable gzip compression. I get this for free on the server, so how do I modify the client to turn this on...?

It turns out that the HttpURLConnection created for you by URL.openConnection already adds the correct header to the request:

Accept-Encoding: gzip

and automatically decompresses the response for you (the stream returned from connection.getInputStream is actually a GZIPInputStream).

Excellent. I hooked up Fiddler as a proxy for the Android emulator to verify, and I could see the gzip header in the request and the compressed response; I could also see much smaller chunks of data in my Azure dashboard.

The spanner in the works

So now, like a good Web API citizen, I wanted to secure my connections using https - that's fine I thought, I should just need to change the url to https:// and it will all just carry on working. Erm, no.

First off, the Azure certificate is not in the certificate store on older versions of Android (that's going to be my next blog post!)

The second problem is that the HttpsURLConnection created for you for a https:// url does not send the gzip request header. Ok, that's easy enough to add:

    connection.setRequestProperty("accept-encoding", "gzip");

However, because we have manually added the gzip header we also need to handle decompressing the data. We can do that by wrapping the response stream in our own GZIPInputStream, giving the following full solution:

private InputStream openUrlStream(String url) throws IOException {
    URLConnection connection = new URL(url).openConnection();
    fixRequestCompression(connection);
    return fixResponseStreamDecompression(connection, connection.getInputStream());
}

private InputStream fixResponseStreamDecompression(URLConnection connection, InputStream inputStream) throws IOException {
    // Because we had to manually request gzip compression, we also have to manually decompress the resultant zipped stream
    String contentEncoding = connection.getContentEncoding();
    if ((contentEncoding != null) && contentEncoding.equalsIgnoreCase("gzip")) {
        if (!(inputStream instanceof GZIPInputStream)) {
            return new GZIPInputStream(inputStream);
        }
    }

    return inputStream;
}

private void fixRequestCompression(URLConnection connection) {
    if (connection instanceof HttpsURLConnection) {
        // gzip compression is enabled by default for HttpURLConnection, but not Https
        connection.setRequestProperty("accept-encoding", "gzip");
    }
}

Notice we only add the request header if it's https, and only wrap the response stream if it is actually compressed and not already being decompressed - this allows the code to work unchanged for http urls.

Et voila, a compressed and secured Web API call.