Blog Infos
Author
Published
Topics
, , , ,
Published

Photo by Marcus Urbenz on Unsplash

Hey there! Today, I want to talk about something simple but super useful — setting up Gzip compression for your API client. Sounds interesting? Let’s dive in!

First of all, what is Gzip?

Gzip is a tool that compresses and decompresses data (originally files) using the Deflate algorithm. In plain terms, it helps reduce the size of data packets sent over the network by compressing them as much as possible.

Note: Even though a gzipped string looks like a stream of unreadable bytes in the console, Gzip is only for request/response compression. If you’re looking for security and want to protect data in transit between your backend and client, you’ll need cryptographic algorithms instead.

Why do I need it?

You might ask me: “Why do I need this stuff, as everything works quickly enough?”. Just an example, what Gzip can do:

Server Response Size in Postman

In the example above, the original response size was nearly half a megabyte. But after compression, it shrank 8x smaller! I deliberately used a large dataset to show how significantly backend responses can be compressed, impacting data flow, network transmission, and overall performance.

It’s important to note that Gzip doesn’t compress all data equally well:

  • Small payloads may not compress efficiently;
  • Different data types compress at different rates.

Server Response Size in Postman

In my case, the API returns JSON. In your case, you’ll need to test based on your data structure.

Less talk, more code

By default, most backends don’t support Gzip (or any compression method) out of the box. That means both the client and the backend need to handle it explicitly. You’ll also need to ensure that both sides send the right headers when processing requests.

In case of Retrofit (I believe you are using it) we can add an Interceptor to do something with requests and responses:

val clientBuilder = OkHttpClient.Builder().addInterceptor { chain ->
  val request = chain.request()
  // doing something with request
  val response = chain.proceed(request)
  // and here we doing something with response
  return@addInterceptor response
}

val retrofit = Retrofit.Builder()
  .baseUrl(BuildConfig.BASE_API_URL)
  .client(clientBuilder.build())
  .build()

I have a feeling this isn’t the first Interceptor in your project, so I won’t go into too much detail about how they work 😉.

Here’s the full implementation of GzipInterceptor:

import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import okio.GzipSource
import okio.buffer

class GzipInterceptor constructor(): Interceptor {
  override fun intercept(chain: Chain): Response {
    val originalRequest = chain.request()
    val compressedRequest = originalRequest.newBuilder()
      /// Add special headers to say the backend that we can handle Gzip
      .header("Accept-Encoding", "gzip, deflate")
      .method(originalRequest.method, originalRequest.body)
      .build()
    val response = chain.proceed(compressedRequest)

    /// If our Response Gzipped, then decompress it, otherwise
    /// we just return the Response
    return if (isGzipped(response)) unzip(response) else response
  }

  private fun unzip(response: Response): Response {
    val body = response.body ?: return response

    /// Decompress response using okio.GzipSource
    val gzipSource = GzipSource(body.source())
    val bodyString = gzipSource.buffer().readUtf8()
    val responseBody = bodyString.toResponseBody(body.contentType())
    val strippedHeaders = response.headers.newBuilder()
      .removeAll("Content-Encoding")
      .removeAll("Content-Length")
      .build()

    return response.newBuilder()
      .headers(strippedHeaders)
      .body(responseBody)
      .message(response.message)
      .build()
  }

  private fun isGzipped(response: Response): Boolean {
    val header = response.header("Content-Encoding")
    return (header != null) && ("gzip" in header)
  }
}

Now add this Interceptor to your network client setup (I suggest to add it the last in the queue, then it will handle the response the first):

val clientBuilder = OkHttpClient.Builder()
  /// our previous interceptors
  .addInterceptor(GzipInterceptor())

What if I’m using Ktor’s HttpClient in a Kotlin Multiplatform project?

You might ask me: “What if I’m using Ktor’s HttpClient in a Kotlin Multiplatform project?” No worries — Ktor makes this easy with built-in plugins for both — the client and the backend.

First of all, we need to add Gradle dependency:

implementation("io.ktor:ktor-client-encoding:$ktor_version")

Job Offers

Job Offers

There are currently no vacancies.

OUR VIDEO RECOMMENDATION

No results found.

Jobs

No results found.

And then we just set up gzip and deflate as plugins to our client:

import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentencoding.*

val client = HttpClient(CIO) {
    install(ContentEncoding) {
        gzip()
        deflate()
    }
}

And that’s it! Now you just need to test it and ship it to your users. That’s all for today — thanks for reading! If you found this helpful, I’d appreciate a like, comment, and follow. 🚀

This article is previously published on proandroiddev.com.

YOU MAY BE INTERESTED IN

YOU MAY BE INTERESTED IN

blog
Using annotations in Kotlin has some nuances that are useful to know
READ MORE
blog
One of the latest trends in UI design is blurring the background content behind the foreground elements. This creates a sense of depth, transparency, and focus,…
READ MORE
blog
Now that Android Studio Iguana is out and stable, I wanted to write about…
READ MORE
blog
The suspension capability is the most essential feature upon which all other Kotlin Coroutines…
READ MORE
Menu