diff --git a/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt b/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt index f0b007c4c..23c8c5a6a 100644 --- a/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt +++ b/okhttp-logging-interceptor/src/main/kotlin/okhttp3/logging/HttpLoggingInterceptor.kt @@ -199,18 +199,30 @@ class HttpLoggingInterceptor @JvmOverloads constructor( } else if (requestBody.isOneShot()) { logger.log("--> END ${request.method} (one-shot body omitted)") } else { - val buffer = Buffer() + var buffer = Buffer() requestBody.writeTo(buffer) + var gzippedLength: Long? = null + if ("gzip".equals(headers["Content-Encoding"], ignoreCase = true)) { + gzippedLength = buffer.size + GzipSource(buffer).use { gzippedResponseBody -> + buffer = Buffer() + buffer.writeAll(gzippedResponseBody) + } + } + val charset: Charset = requestBody.contentType().charset() logger.log("") - if (buffer.isProbablyUtf8()) { + if (!buffer.isProbablyUtf8()) { + logger.log( + "--> END ${request.method} (binary ${requestBody.contentLength()}-byte body omitted)" + ) + } else if (gzippedLength != null) { + logger.log("--> END ${request.method} (${buffer.size}-byte, $gzippedLength-gzipped-byte body)") + } else { logger.log(buffer.readString(charset)) logger.log("--> END ${request.method} (${requestBody.contentLength()}-byte body)") - } else { - logger.log( - "--> END ${request.method} (binary ${requestBody.contentLength()}-byte body omitted)") } } } diff --git a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java index 963ec199a..758358673 100644 --- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java +++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java @@ -22,12 +22,13 @@ import java.util.List; import java.util.regex.Pattern; import javax.annotation.Nullable; import javax.net.ssl.HostnameVerifier; +import mockwebserver3.MockResponse; +import mockwebserver3.MockWebServer; import mockwebserver3.junit5.internal.MockWebServerExtension; import okhttp3.HttpUrl; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; -import okhttp3.testing.PlatformRule; import okhttp3.Protocol; import okhttp3.RecordingHostnameVerifier; import okhttp3.Request; @@ -35,8 +36,7 @@ import okhttp3.RequestBody; import okhttp3.Response; import okhttp3.ResponseBody; import okhttp3.logging.HttpLoggingInterceptor.Level; -import mockwebserver3.MockResponse; -import mockwebserver3.MockWebServer; +import okhttp3.testing.PlatformRule; import okhttp3.tls.HandshakeCertificates; import okio.Buffer; import okio.BufferedSink; @@ -46,6 +46,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; +import static okhttp3.RequestBody.gzip; import static okhttp3.tls.internal.TlsUtil.localhost; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.fail; @@ -580,6 +581,42 @@ public final class HttpLoggingInterceptorTest { .assertNoMoreLogs(); } + @Test public void bodyRequestGzipEncoded() throws IOException { + setLevel(Level.BODY); + + server.enqueue(new MockResponse() + .setHeader("Content-Type", PLAIN) + .setBody(new Buffer().writeUtf8("Uncompressed"))); + + Response response = client.newCall(request() + .addHeader("Content-Encoding", "gzip") + .post(gzip(RequestBody.create("Uncompressed", null))) + .build()).execute(); + + ResponseBody responseBody = response.body(); + assertThat(responseBody.string()).overridingErrorMessage( + "Expected response body to be valid").isEqualTo("Uncompressed"); + responseBody.close(); + + networkLogs + .assertLogEqual("--> POST " + url + " http/1.1") + .assertLogEqual("Content-Encoding: gzip") + .assertLogEqual("Transfer-Encoding: chunked") + .assertLogEqual("Host: " + host) + .assertLogEqual("Connection: Keep-Alive") + .assertLogEqual("Accept-Encoding: gzip") + .assertLogMatch("User-Agent: okhttp/.+") + .assertLogEqual("") + .assertLogEqual("--> END POST (12-byte, 32-gzipped-byte body)") + .assertLogMatch("<-- 200 OK " + url + " \\(\\d+ms\\)") + .assertLogEqual("Content-Type: text/plain; charset=utf-8") + .assertLogMatch("Content-Length: \\d+") + .assertLogEqual("") + .assertLogEqual("Uncompressed") + .assertLogEqual("<-- END HTTP (12-byte body)") + .assertNoMoreLogs(); + } + @Test public void bodyResponseGzipEncoded() throws IOException { setLevel(Level.BODY); diff --git a/okhttp/src/jvmMain/kotlin/okhttp3/RequestBody.kt b/okhttp/src/jvmMain/kotlin/okhttp3/RequestBody.kt index a9ba7ba37..9696d200f 100644 --- a/okhttp/src/jvmMain/kotlin/okhttp3/RequestBody.kt +++ b/okhttp/src/jvmMain/kotlin/okhttp3/RequestBody.kt @@ -23,6 +23,8 @@ import okhttp3.internal.checkOffsetAndCount import okhttp3.internal.chooseCharset import okio.BufferedSink import okio.ByteString +import okio.GzipSink +import okio.buffer import okio.source abstract class RequestBody { @@ -224,5 +226,40 @@ abstract class RequestBody { ), level = DeprecationLevel.WARNING) fun create(contentType: MediaType?, file: File): RequestBody= file.asRequestBody(contentType) + + /** + * Returns a gzip version of the RequestBody, with compressed payload. + * This is not automatic as not all servers support gzip compressed requests. + * + * ``` + * val request = Request.Builder().url("...") + * .addHeader("Content-Encoding", "gzip") + * .post(uncompressedBody.gzip()) + * .build() + * ``` + */ + @JvmStatic + fun RequestBody.gzip(): RequestBody { + return object : RequestBody() { + override fun contentType(): MediaType? { + return this@gzip.contentType() + } + + override fun contentLength(): Long { + return -1 // We don't know the compressed length in advance! + } + + @Throws(IOException::class) + override fun writeTo(sink: BufferedSink) { + val gzipSink = GzipSink(sink).buffer() + this@gzip.writeTo(gzipSink) + gzipSink.close() + } + + override fun isOneShot(): Boolean { + return this@gzip.isOneShot() + } + } + } } }