diff --git a/android-test/build.gradle.kts b/android-test/build.gradle.kts index a40dd0b95..8b97915e9 100644 --- a/android-test/build.gradle.kts +++ b/android-test/build.gradle.kts @@ -91,6 +91,7 @@ dependencies { androidTestImplementation(projects.mockwebserver3Junit4) androidTestImplementation(projects.mockwebserver3Junit5) androidTestImplementation(projects.okhttpBrotli) + androidTestImplementation(projects.okhttpZstd) androidTestImplementation(projects.okhttpDnsoverhttps) androidTestImplementation(projects.loggingInterceptor) androidTestImplementation(projects.okhttpSse) diff --git a/android-test/src/androidTest/java/okhttp/android/test/OkHttpTest.kt b/android-test/src/androidTest/java/okhttp/android/test/OkHttpTest.kt index 28702a41f..079532c93 100644 --- a/android-test/src/androidTest/java/okhttp/android/test/OkHttpTest.kt +++ b/android-test/src/androidTest/java/okhttp/android/test/OkHttpTest.kt @@ -48,6 +48,8 @@ import mockwebserver3.junit5.StartStop import okhttp3.Cache import okhttp3.Call import okhttp3.CertificatePinner +import okhttp3.CompressionInterceptor +import okhttp3.CompressionInterceptor.Companion.Gzip import okhttp3.Connection import okhttp3.DelegatingSSLSocket import okhttp3.DelegatingSSLSocketFactory @@ -60,6 +62,7 @@ import okhttp3.Protocol import okhttp3.RecordingEventListener import okhttp3.Request import okhttp3.TlsVersion +import okhttp3.brotli.Brotli import okhttp3.dnsoverhttps.DnsOverHttps import okhttp3.internal.concurrent.TaskRunner import okhttp3.internal.http2.Http2 @@ -71,6 +74,7 @@ import okhttp3.logging.LoggingEventListener import okhttp3.testing.PlatformRule import okhttp3.tls.HandshakeCertificates import okhttp3.tls.internal.TlsUtil.localhost +import okhttp3.zstd.Zstd import okio.ByteString.Companion.toByteString import org.bouncycastle.jce.provider.BouncyCastleProvider import org.bouncycastle.jsse.provider.BouncyCastleJsseProvider @@ -108,7 +112,11 @@ class OkHttpTest { logger = Logger.getLogger(OkHttpTest::class.java.name) } - private var client: OkHttpClient = clientTestRule.newClient() + private var client: OkHttpClient = + clientTestRule + .newClientBuilder() + .addInterceptor(CompressionInterceptor(Zstd, Brotli, Gzip)) + .build() private val moshi = Moshi diff --git a/okhttp-brotli/api/okhttp-brotli.api b/okhttp-brotli/api/okhttp-brotli.api index df2454bd2..ce1008f00 100644 --- a/okhttp-brotli/api/okhttp-brotli.api +++ b/okhttp-brotli/api/okhttp-brotli.api @@ -1,5 +1,8 @@ -public final class okhttp3/brotli/BrotliInterceptor : okhttp3/Interceptor { +public final class okhttp3/brotli/BrotliInterceptor : okhttp3/CompressionInterceptor { public static final field INSTANCE Lokhttp3/brotli/BrotliInterceptor; - public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; +} + +public final class okhttp3/brotli/BrotliInterceptorKt { + public static final fun getBrotli ()Lokhttp3/CompressionInterceptor$DecompressionAlgorithm; } diff --git a/okhttp-brotli/build.gradle.kts b/okhttp-brotli/build.gradle.kts index f36c0a546..15ecef065 100644 --- a/okhttp-brotli/build.gradle.kts +++ b/okhttp-brotli/build.gradle.kts @@ -15,7 +15,7 @@ project.applyOsgi( ) dependencies { - api(projects.okhttp) + "friendsApi"(projects.okhttp) api(libs.brotli.dec) testImplementation(projects.okhttpTestingSupport) diff --git a/okhttp-brotli/src/main/kotlin/okhttp3/brotli/BrotliInterceptor.kt b/okhttp-brotli/src/main/kotlin/okhttp3/brotli/BrotliInterceptor.kt index 7d94ecea6..fb0d28d2f 100644 --- a/okhttp-brotli/src/main/kotlin/okhttp3/brotli/BrotliInterceptor.kt +++ b/okhttp-brotli/src/main/kotlin/okhttp3/brotli/BrotliInterceptor.kt @@ -15,9 +15,11 @@ */ package okhttp3.brotli -import okhttp3.Interceptor -import okhttp3.Response -import okhttp3.brotli.internal.uncompress +import okhttp3.CompressionInterceptor +import okio.BufferedSource +import okio.Source +import okio.source +import org.brotli.dec.BrotliInputStream /** * Transparent Brotli response support. @@ -25,20 +27,11 @@ import okhttp3.brotli.internal.uncompress * Adds Accept-Encoding: br to request and checks (and strips) for Content-Encoding: br in * responses. n.b. this replaces the transparent gzip compression in BridgeInterceptor. */ -object BrotliInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response = - if (chain.request().header("Accept-Encoding") == null) { - val request = - chain - .request() - .newBuilder() - .header("Accept-Encoding", "br,gzip") - .build() +object BrotliInterceptor : CompressionInterceptor(Brotli, Gzip) - val response = chain.proceed(request) +val Brotli = + object : CompressionInterceptor.DecompressionAlgorithm { + override val encoding: String = "br" - uncompress(response) - } else { - chain.proceed(chain.request()) - } -} + override fun decompress(compressedSource: BufferedSource): Source = BrotliInputStream(compressedSource.inputStream()).source() + } diff --git a/okhttp-brotli/src/main/kotlin/okhttp3/brotli/internal/Uncompress.kt b/okhttp-brotli/src/main/kotlin/okhttp3/brotli/internal/Uncompress.kt deleted file mode 100644 index c79d199b8..000000000 --- a/okhttp-brotli/src/main/kotlin/okhttp3/brotli/internal/Uncompress.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (C) 2020 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package okhttp3.brotli.internal - -import okhttp3.Response -import okhttp3.ResponseBody.Companion.asResponseBody -import okhttp3.internal.http.promisesBody -import okio.GzipSource -import okio.buffer -import okio.source -import org.brotli.dec.BrotliInputStream - -fun uncompress(response: Response): Response { - if (!response.promisesBody()) { - return response - } - val body = response.body - val encoding = response.header("Content-Encoding") ?: return response - - val decompressedSource = - when { - encoding.equals("br", ignoreCase = true) -> - BrotliInputStream(body.source().inputStream()).source().buffer() - encoding.equals("gzip", ignoreCase = true) -> - GzipSource(body.source()).buffer() - else -> return response - } - - return response - .newBuilder() - .removeHeader("Content-Encoding") - .removeHeader("Content-Length") - .body(decompressedSource.asResponseBody(body.contentType(), -1)) - .build() -} diff --git a/okhttp-brotli/src/test/java/okhttp3/brotli/BrotliInterceptorTest.kt b/okhttp-brotli/src/test/java/okhttp3/brotli/BrotliInterceptorTest.kt index b0338ed73..e3bc79086 100644 --- a/okhttp-brotli/src/test/java/okhttp3/brotli/BrotliInterceptorTest.kt +++ b/okhttp-brotli/src/test/java/okhttp3/brotli/BrotliInterceptorTest.kt @@ -27,7 +27,6 @@ import okhttp3.Protocol import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody.Companion.toResponseBody -import okhttp3.brotli.internal.uncompress import okio.ByteString import okio.ByteString.Companion.EMPTY import okio.ByteString.Companion.decodeHex @@ -48,7 +47,7 @@ class BrotliInterceptorTest { header("Content-Encoding", "br") } - val uncompressed = uncompress(response) + val uncompressed = BrotliInterceptor.decompress(response) val responseString = uncompressed.body.string() assertThat(responseString).contains("\"brotli\": true,") @@ -69,7 +68,7 @@ class BrotliInterceptorTest { header("Content-Encoding", "gzip") } - val uncompressed = uncompress(response) + val uncompressed = BrotliInterceptor.decompress(response) val responseString = uncompressed.body.string() assertThat(responseString).contains("\"gzipped\": true,") @@ -80,7 +79,7 @@ class BrotliInterceptorTest { fun testNoUncompress() { val response = response("https://httpbin.org/brotli", "XXXX".encodeUtf8()) - val same = uncompress(response) + val same = BrotliInterceptor.decompress(response) val responseString = same.body.string() assertThat(responseString).isEqualTo("XXXX") @@ -94,7 +93,7 @@ class BrotliInterceptorTest { } assertFailsWith { - val failingResponse = uncompress(response) + val failingResponse = BrotliInterceptor.decompress(response) failingResponse.body.string() }.also { ioe -> assertThat(ioe).hasMessage("Brotli stream decoding failed") @@ -111,7 +110,7 @@ class BrotliInterceptorTest { message("NO CONTENT") } - val same = uncompress(response) + val same = BrotliInterceptor.decompress(response) val responseString = same.body.string() assertThat(responseString).isEmpty() diff --git a/okhttp-zstd/api/okhttp-zstd.api b/okhttp-zstd/api/okhttp-zstd.api index af53dcaff..ca137ca54 100644 --- a/okhttp-zstd/api/okhttp-zstd.api +++ b/okhttp-zstd/api/okhttp-zstd.api @@ -1,5 +1,4 @@ -public final class okhttp3/zstd/ZstdInterceptor : okhttp3/Interceptor { - public static final field INSTANCE Lokhttp3/zstd/ZstdInterceptor; - public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; +public final class okhttp3/zstd/ZstdInterceptorKt { + public static final fun getZstd ()Lokhttp3/CompressionInterceptor$DecompressionAlgorithm; } diff --git a/okhttp-zstd/build.gradle.kts b/okhttp-zstd/build.gradle.kts index 359eb646a..6f1fe7bd2 100644 --- a/okhttp-zstd/build.gradle.kts +++ b/okhttp-zstd/build.gradle.kts @@ -15,7 +15,7 @@ project.applyOsgi( ) dependencies { - api(projects.okhttp) + "friendsApi"(projects.okhttp) implementation(libs.zstd.kmp.okio) testImplementation(projects.okhttpTestingSupport) diff --git a/okhttp-zstd/src/main/kotlin/okhttp3/zstd/ZstdInterceptor.kt b/okhttp-zstd/src/main/kotlin/okhttp3/zstd/ZstdInterceptor.kt index 82d3dd41e..a9a8b1044 100644 --- a/okhttp-zstd/src/main/kotlin/okhttp3/zstd/ZstdInterceptor.kt +++ b/okhttp-zstd/src/main/kotlin/okhttp3/zstd/ZstdInterceptor.kt @@ -16,60 +16,16 @@ package okhttp3.zstd import com.squareup.zstd.okio.zstdDecompress -import okhttp3.Interceptor -import okhttp3.Response -import okhttp3.ResponseBody.Companion.asResponseBody -import okhttp3.internal.http.promisesBody -import okio.buffer -import okio.gzip +import okhttp3.CompressionInterceptor +import okio.BufferedSource +import okio.Source /** - * Transparent Zstandard response support. - * - * This must be installed as an application interceptor. - * - * Adds `Accept-Encoding: zstd,gzip` to request and checks (and strips) for `Content-Encoding: zstd` - * in responses. - * - * This replaces the transparent gzip compression in OkHttp. + * Support for Zstandard encoding. Use via the [CompressionInterceptor]. */ -object ZstdInterceptor : Interceptor { - override fun intercept(chain: Interceptor.Chain): Response { - val originalRequest = chain.request() +val Zstd = + object : CompressionInterceptor.DecompressionAlgorithm { + override val encoding: String = "zstd" - if (originalRequest.header("Accept-Encoding") != null) { - return chain.proceed(originalRequest) - } - - val request = - originalRequest - .newBuilder() - .header("Accept-Encoding", "zstd,gzip") - .build() - - return decompress(chain.proceed(request)) + override fun decompress(compressedSource: BufferedSource): Source = compressedSource.zstdDecompress() } - - internal fun decompress(response: Response): Response { - if (!response.promisesBody()) { - return response - } - - val body = response.body - val encoding = response.header("Content-Encoding") ?: return response - - val decompressedSource = - when { - encoding.equals("zstd", ignoreCase = true) -> body.source().zstdDecompress() - encoding.equals("gzip", ignoreCase = true) -> body.source().gzip() - else -> return response - } - - return response - .newBuilder() - .removeHeader("Content-Encoding") - .removeHeader("Content-Length") - .body(decompressedSource.buffer().asResponseBody(body.contentType(), -1L)) - .build() - } -} diff --git a/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdInterceptorTest.kt b/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdInterceptorTest.kt index 52092bc1c..8da4ab791 100644 --- a/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdInterceptorTest.kt +++ b/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdInterceptorTest.kt @@ -23,12 +23,12 @@ import assertk.assertions.isNull import com.squareup.zstd.okio.zstdCompress import java.io.IOException import kotlin.test.assertFailsWith +import okhttp3.CompressionInterceptor import okhttp3.MediaType.Companion.toMediaType import okhttp3.Protocol import okhttp3.Request import okhttp3.Response import okhttp3.ResponseBody.Companion.toResponseBody -import okhttp3.zstd.ZstdInterceptor.decompress import okio.Buffer import okio.ByteString import okio.ByteString.Companion.EMPTY @@ -39,6 +39,8 @@ import okio.gzip import org.junit.jupiter.api.Test class ZstdInterceptorTest { + val zstdInterceptor = CompressionInterceptor(Zstd, CompressionInterceptor.Gzip) + @Test fun testDecompressZstd() { val s = "hello zstd world".encodeUtf8().zstdCompress() @@ -48,7 +50,7 @@ class ZstdInterceptorTest { header("Content-Encoding", "zstd") } - val decompressed = decompress(response) + val decompressed = zstdInterceptor.decompress(response) assertThat(decompressed.header("Content-Encoding")).isNull() val responseString = decompressed.body.string() @@ -64,7 +66,7 @@ class ZstdInterceptorTest { header("Content-Encoding", "gzip") } - val decompressed = decompress(response) + val decompressed = zstdInterceptor.decompress(response) assertThat(decompressed.header("Content-Encoding")).isNull() val responseString = decompressed.body.string() @@ -77,7 +79,7 @@ class ZstdInterceptorTest { val response = response("https://example.com/", s) - val decompressed = decompress(response) + val decompressed = zstdInterceptor.decompress(response) assertThat(decompressed.header("Content-Encoding")).isNull() val responseString = decompressed.body.string() @@ -93,7 +95,7 @@ class ZstdInterceptorTest { header("Content-Encoding", "deflate") } - val decompressed = decompress(response) + val decompressed = zstdInterceptor.decompress(response) assertThat(decompressed.header("Content-Encoding")).isEqualTo("deflate") val responseString = decompressed.body.string() @@ -109,7 +111,7 @@ class ZstdInterceptorTest { header("Content-Encoding", "zstd") } - val decompressed = decompress(response) + val decompressed = zstdInterceptor.decompress(response) assertThat(decompressed.header("Content-Encoding")).isNull() assertFailsWith { @@ -128,7 +130,7 @@ class ZstdInterceptorTest { message("NO CONTENT") } - val same = decompress(response) + val same = zstdInterceptor.decompress(response) val responseString = same.body.string() assertThat(responseString).isEmpty() diff --git a/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdTestMain.kt b/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdTestMain.kt index 4d7388f0c..c8c3a351e 100644 --- a/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdTestMain.kt +++ b/okhttp-zstd/src/test/java/okhttp3/zstd/ZstdTestMain.kt @@ -15,6 +15,7 @@ */ package okhttp3.zstd +import okhttp3.CompressionInterceptor import okhttp3.OkHttpClient import okhttp3.Request @@ -22,7 +23,7 @@ fun main() { val client = OkHttpClient .Builder() - .addInterceptor(ZstdInterceptor) + .addInterceptor(CompressionInterceptor(Zstd)) .build() sendRequest("https://developers.facebook.com/docs/", client) diff --git a/okhttp/api/android/okhttp.api b/okhttp/api/android/okhttp.api index 7dccfa7f1..792bbbdd8 100644 --- a/okhttp/api/android/okhttp.api +++ b/okhttp/api/android/okhttp.api @@ -326,6 +326,22 @@ public final class okhttp3/CipherSuite$Companion { public final fun forJavaName (Ljava/lang/String;)Lokhttp3/CipherSuite; } +public class okhttp3/CompressionInterceptor : okhttp3/Interceptor { + public static final field Companion Lokhttp3/CompressionInterceptor$Companion; + public fun ([Lokhttp3/CompressionInterceptor$DecompressionAlgorithm;)V + public final fun getAlgorithms ()[Lokhttp3/CompressionInterceptor$DecompressionAlgorithm; + public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; +} + +public final class okhttp3/CompressionInterceptor$Companion { + public final fun getGzip ()Lokhttp3/CompressionInterceptor$DecompressionAlgorithm; +} + +public abstract interface class okhttp3/CompressionInterceptor$DecompressionAlgorithm { + public abstract fun decompress (Lokio/BufferedSource;)Lokio/Source; + public abstract fun getEncoding ()Ljava/lang/String; +} + public abstract interface class okhttp3/Connection { public abstract fun handshake ()Lokhttp3/Handshake; public abstract fun protocol ()Lokhttp3/Protocol; diff --git a/okhttp/api/jvm/okhttp.api b/okhttp/api/jvm/okhttp.api index e0c2d0e41..fbfd15aac 100644 --- a/okhttp/api/jvm/okhttp.api +++ b/okhttp/api/jvm/okhttp.api @@ -326,6 +326,22 @@ public final class okhttp3/CipherSuite$Companion { public final fun forJavaName (Ljava/lang/String;)Lokhttp3/CipherSuite; } +public class okhttp3/CompressionInterceptor : okhttp3/Interceptor { + public static final field Companion Lokhttp3/CompressionInterceptor$Companion; + public fun ([Lokhttp3/CompressionInterceptor$DecompressionAlgorithm;)V + public final fun getAlgorithms ()[Lokhttp3/CompressionInterceptor$DecompressionAlgorithm; + public fun intercept (Lokhttp3/Interceptor$Chain;)Lokhttp3/Response; +} + +public final class okhttp3/CompressionInterceptor$Companion { + public final fun getGzip ()Lokhttp3/CompressionInterceptor$DecompressionAlgorithm; +} + +public abstract interface class okhttp3/CompressionInterceptor$DecompressionAlgorithm { + public abstract fun decompress (Lokio/BufferedSource;)Lokio/Source; + public abstract fun getEncoding ()Ljava/lang/String; +} + public abstract interface class okhttp3/Connection { public abstract fun handshake ()Lokhttp3/Handshake; public abstract fun protocol ()Lokhttp3/Protocol; diff --git a/okhttp/src/commonJvmAndroid/kotlin/okhttp3/CompressionInterceptor.kt b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/CompressionInterceptor.kt new file mode 100644 index 000000000..d52eb6124 --- /dev/null +++ b/okhttp/src/commonJvmAndroid/kotlin/okhttp3/CompressionInterceptor.kt @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2025 Block, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3 + +import okhttp3.ResponseBody.Companion.asResponseBody +import okhttp3.internal.http.promisesBody +import okio.BufferedSource +import okio.GzipSource +import okio.Source +import okio.buffer + +/** + * Transparent Compressed response support. + * + * The algorithm map will be turned into a heading such as "Accept-Encoding: br, gzip" + * + * If [algorithms] is empty this interceptor has no effect. To disable compression set + * a specific "Accept-Encoding: identity" or similar. + * + * See https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Accept-Encoding + */ +open class CompressionInterceptor( + vararg val algorithms: DecompressionAlgorithm, +) : Interceptor { + internal val acceptEncoding = + algorithms + .map { + it.encoding + }.joinToString(separator = ", ") + + override fun intercept(chain: Interceptor.Chain): Response = + if (algorithms.isNotEmpty() && chain.request().header("Accept-Encoding") == null) { + val request = + chain + .request() + .newBuilder() + .header("Accept-Encoding", acceptEncoding) + .build() + + val response = chain.proceed(request) + + decompress(response) + } else { + chain.proceed(chain.request()) + } + + /** + * Returns a decompressed copy of the Response, typically via a streaming Source. + * If no known decompression or the response is not compressed, returns the response unmodified. + */ + internal fun decompress(response: Response): Response { + if (!response.promisesBody()) { + return response + } + val body = response.body + val encoding = response.header("Content-Encoding") ?: return response + + val algorithm = lookupDecompressor(encoding) ?: return response + + val decompressedSource = algorithm.decompress(body.source()).buffer() + + return response + .newBuilder() + .removeHeader("Content-Encoding") + .removeHeader("Content-Length") + .body(decompressedSource.asResponseBody(body.contentType(), -1)) + .build() + } + + internal fun lookupDecompressor(encoding: String): DecompressionAlgorithm? = + algorithms.find { + it.encoding.equals(encoding, ignoreCase = true) + } + + /** + * A decompression algorithm such as Gzip. Must provide the Accept-Encoding value and decompress a Source. + */ + interface DecompressionAlgorithm { + val encoding: String + + fun decompress(compressedSource: BufferedSource): Source + } + + companion object { + /** + * Request "gzip" compression. + */ + val Gzip = + object : DecompressionAlgorithm { + override val encoding: String = "gzip" + + override fun decompress(compressedSource: BufferedSource): Source = GzipSource(compressedSource) + } + } +} diff --git a/okhttp/src/commonTest/kotlin/okhttp3/CompressionInterceptorTest.kt b/okhttp/src/commonTest/kotlin/okhttp3/CompressionInterceptorTest.kt new file mode 100644 index 000000000..040bf35f4 --- /dev/null +++ b/okhttp/src/commonTest/kotlin/okhttp3/CompressionInterceptorTest.kt @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2025 Block, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package okhttp3 + +import assertk.assertThat +import assertk.assertions.isEqualTo +import assertk.assertions.isNull +import okhttp3.CompressionInterceptor.Companion.Gzip +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.ResponseBody.Companion.asResponseBody +import okhttp3.ResponseBody.Companion.toResponseBody +import okio.Buffer +import okio.ByteString.Companion.encodeUtf8 +import okio.GzipSink +import okio.Source +import okio.buffer +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.extension.RegisterExtension + +class CompressionInterceptorTest { + @RegisterExtension + val clientTestRule = OkHttpClientTestRule() + + val source = + Buffer().apply { + write("Hello World".encodeUtf8()) + } as Source + + @Test + fun emptyDoesntChangeRequestOrResponse() { + val empty = CompressionInterceptor() + val client = + clientTestRule + .newClientBuilder() + .addInterceptor(empty) + .addInterceptor { chain -> + assertThat(chain.request().header("Accept-Encoding")).isNull() + Response + .Builder() + .request(chain.request()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body("Hello".toResponseBody()) + .header("Content-Encoding", "piedpiper") + .build() + }.build() + + val response = client.newCall(Request("https://google.com/robots.txt".toHttpUrl())).execute() + + assertThat(response.header("Content-Encoding")).isEqualTo("piedpiper") + assertThat(response.body.string()).isEqualTo("Hello") + } + + @Test + fun gzipThroughCall() { + val gzip = CompressionInterceptor(Gzip) + val client = + clientTestRule + .newClientBuilder() + .addInterceptor(gzip) + .addInterceptor { chain -> + assertThat(chain.request().header("Accept-Encoding")).isEqualTo("gzip") + + Response + .Builder() + .request(chain.request()) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body(gzip("Hello").asResponseBody()) + .header("Content-Encoding", "gzip") + .build() + }.build() + + val response = client.newCall(Request("https://google.com/robots.txt".toHttpUrl())).execute() + + assertThat(response.header("Content-Encoding")).isNull() + assertThat(response.body.string()).isEqualTo("Hello") + } + + private fun gzip(data: String): Buffer { + val result = Buffer() + val sink = GzipSink(result).buffer() + sink.writeUtf8(data) + sink.close() + return result + } +}