Привіт! Сьогодні хочеться написати про щось просте, проте корисне, і я вирішив, що було б непогано показати і розказати, як налаштувати Gzip-кодування для API. Цікаво? Тоді поїхали розбиратись!
Ітак, що таке Gzip? Gzip — це тулза, яка стискає та розпаковує дані (в оригіналі файли) за допомогою алгоритму Deflate. Простими словами, це потрібно, щоб зменшити пакети даних, які передаются по мережі, максимально стиснувши їх.
Note: хоча gzipped-рядок виглядає як незрозумілий потік байтів в консолі, Gzip — це тулза виключно для стискання реквестів та респонзів. Якщо потрібно щось для захисту, щоб дані, які ви ганяєте між бекендом і клієнтом, були захищеними від атак, потрібно шукати алгоритми криптографії.
Наглядний приклад, нащо потрібен Gzip:
На зображенні вище можна побачити, що оригінальний розмір даних був майже пів мегабайта, але після компресії, він став у 8 разів менше. Для прикладу, я спеціально взяв досить великий обʼєм даних, щоб продемонструвати як сильно можна зжати відповідь від бекенда, та як це може вплинути на потік даних, їх передачу по мережі ітд. Слід також розуміти, що Gzip неоднаково гарно стискає різні типи і різні обʼєми даних:
В моїх прикладах API повертає JSON. В вашому випадку необхідно тестувати на ваших даних.
Менше слів — більш коду
Важливо також зазначити, що з коробки бекенд не підтримує ані Gzip, ані будь-який інший алгоритм стискання/розпаковки. Відповідно, це має бути імплементоване на обох сторонах — на клієнті та бекенді. Також, клієнт та бекенд мають надсилати відповідні заголовки в процесі обробки запитів.
Скоріш за все, в вашому проекті Retrofit. Відповідно, коли ми ініціалізуємо Retrofit та OkHttp, ми можемо додати Interceptor для взаємодії із Request та Response:
val clientBuilder = OkHttpClient.Builder().addInterceptor { chain ->
val request = chain.request()
// робимо щось круте із request
val response = chain.proceed(request)
// а тут робимо щось круте із response
return@addInterceptor response
}val retrofit = Retrofit.Builder()
.baseUrl(BuildConfig.BASE_API_URL)
.client(clientBuilder.build())
.build()
Є в мене підозра, що це не перший interceptor в вашому проекті, а, отже, не буду зупинятись детально на принципі їх роботи 🙂 Повний приклад коду GzipInterceptor:
import okhttp3.Interceptor
import okhttp3.Interceptor.Chain
import okhttp3.Response
import okhttp3.ResponseBody.Companion.toResponseBody
import okio.GzipSource
import okio.bufferclass GzipInterceptor constructor(): Interceptor {
override fun intercept(chain: Chain): Response {
val originalRequest = chain.request()
val compressedRequest = originalRequest.newBuilder()
/// додаємо заголовки в запит, щоб бекенд знав, що ми вміємо в Gzip
.header("Accept-Encoding", "gzip, deflate")
.method(originalRequest.method, originalRequest.body)
.build()
val response = chain.proceed(compressedRequest)
/// якщо Response запакований задопомогою Gzip, тоді розпаковуємо,
/// інакше - просто повертаємо оригінальну відповідь
return if (isGzipped(response)) unzip(response) else response
}
private fun unzip(response: Response): Response {
val body = response.body ?: return response
/// бекенд прислав запакований Response, який ми розпаковуємо
/// задопомогою 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)
}
}
Підключаємо в список Interceptor’ів (раджу підключати останнім в черзі — тоді він останнім похендлить запит і першим — відповідь):
val clientBuilder = OkHttpClient.Builder()
/// попередні Interceptor'и, які в нас були
.addInterceptor(GzipInterceptor())
Не Retrofit єдиним
“А якщо в мене Kotlin Multiplatform, і я використовую Ktor HttpClient?” — можеш запитати мене ти. В такому разі все також досить просто — Ktor підтримує і має відповідні плагіни для цього як на клієнті, так і на бекенді.
Імпорт плагіна в Gradle:
implementation("io.ktor:ktor-client-encoding:$ktor_version")
Конфігурація самого клієнта:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.client.plugins.contentencoding.*val client = HttpClient(CIO) {
install(ContentEncoding) {
gzip()
deflate()
}
}
От і все. Тепер залишається протестувати і відправити юзерам. А в мене на цьому все. Дякую, що дочитав до кінця, буду вдячний за лайк комент та підписку 🙂