From bb304b9c2cfc02fbbee3e3a99a360e1d25e37b48 Mon Sep 17 00:00:00 2001 From: Matt Sheppard Date: Mon, 29 Jan 2018 03:33:00 +1100 Subject: [PATCH] Log gzipped bodies when HttpLoggingInterceptor is used as a NetworkInterceptor (#3792) * (in-progress) Support gzipped bodies as a networkInterceptor * Fixed buffer cloning, added test for a still-unsupported encoding (Brotli) * Avoid try-with-resources and too-long lines to appease build checks * Fixed method name typo * Added suggested comma between byte and gzipped-byte count * Account for added comma in test * Use buffer.writeAll to ensure all body content is read * Indentation consistency * Added test to confirm response body remains valid --- .../logging/HttpLoggingInterceptor.java | 33 +++++++++++-- .../logging/HttpLoggingInterceptorTest.java | 49 +++++++++++++++++-- 2 files changed, 74 insertions(+), 8 deletions(-) diff --git a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java index 2eb8100a4..e088ea7c6 100644 --- a/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java +++ b/okhttp-logging-interceptor/src/main/java/okhttp3/logging/HttpLoggingInterceptor.java @@ -32,6 +32,7 @@ import okhttp3.internal.http.HttpHeaders; import okhttp3.internal.platform.Platform; import okio.Buffer; import okio.BufferedSource; +import okio.GzipSource; import static okhttp3.internal.platform.Platform.INFO; @@ -182,7 +183,7 @@ public final class HttpLoggingInterceptor implements Interceptor { if (!logBody || !hasRequestBody) { logger.log("--> END " + request.method()); - } else if (bodyEncoded(request.headers())) { + } else if (bodyHasUnknownEncoding(request.headers())) { logger.log("--> END " + request.method() + " (encoded body omitted)"); } else { Buffer buffer = new Buffer(); @@ -233,13 +234,28 @@ public final class HttpLoggingInterceptor implements Interceptor { if (!logBody || !HttpHeaders.hasBody(response)) { logger.log("<-- END HTTP"); - } else if (bodyEncoded(response.headers())) { + } else if (bodyHasUnknownEncoding(response.headers())) { logger.log("<-- END HTTP (encoded body omitted)"); } else { BufferedSource source = responseBody.source(); source.request(Long.MAX_VALUE); // Buffer the entire body. Buffer buffer = source.buffer(); + Long gzippedLength = null; + if ("gzip".equalsIgnoreCase(headers.get("Content-Encoding"))) { + gzippedLength = buffer.size(); + GzipSource gzippedResponseBody = null; + try { + gzippedResponseBody = new GzipSource(buffer.clone()); + buffer = new Buffer(); + buffer.writeAll(gzippedResponseBody); + } finally { + if (gzippedResponseBody != null) { + gzippedResponseBody.close(); + } + } + } + Charset charset = UTF8; MediaType contentType = responseBody.contentType(); if (contentType != null) { @@ -257,7 +273,12 @@ public final class HttpLoggingInterceptor implements Interceptor { logger.log(buffer.clone().readString(charset)); } - logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); + if (gzippedLength != null) { + logger.log("<-- END HTTP (" + buffer.size() + "-byte, " + + gzippedLength + "-gzipped-byte body)"); + } else { + logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); + } } } @@ -288,8 +309,10 @@ public final class HttpLoggingInterceptor implements Interceptor { } } - private boolean bodyEncoded(Headers headers) { + private boolean bodyHasUnknownEncoding(Headers headers) { String contentEncoding = headers.get("Content-Encoding"); - return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); + return contentEncoding != null + && !contentEncoding.equalsIgnoreCase("identity") + && !contentEncoding.equalsIgnoreCase("gzip"); } } 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 108ab18a7..a80b0404a 100644 --- a/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java +++ b/okhttp-logging-interceptor/src/test/java/okhttp3/logging/HttpLoggingInterceptorTest.java @@ -31,6 +31,7 @@ import okhttp3.RecordingHostnameVerifier; import okhttp3.Request; import okhttp3.RequestBody; import okhttp3.Response; +import okhttp3.ResponseBody; import okhttp3.internal.tls.SslClient; import okhttp3.logging.HttpLoggingInterceptor.Level; import okhttp3.mockwebserver.MockResponse; @@ -532,7 +533,7 @@ public final class HttpLoggingInterceptorTest { .assertNoMoreLogs(); } - @Test public void bodyResponseNotIdentityEncoded() throws IOException { + @Test public void bodyResponseGzipEncoded() throws IOException { setLevel(Level.BODY); server.enqueue(new MockResponse() @@ -541,7 +542,10 @@ public final class HttpLoggingInterceptorTest { .setBody(new Buffer().write(ByteString.decodeBase64( "H4sIAAAAAAAAAPNIzcnJ11HwQKIAdyO+9hMAAAA=")))); Response response = client.newCall(request().build()).execute(); - response.body().close(); + + ResponseBody responseBody = response.body(); + assertEquals("Expected response body to be valid","Hello, Hello, Hello", responseBody.string()); + responseBody.close(); networkLogs .assertLogEqual("--> GET " + url + " http/1.1") @@ -554,7 +558,9 @@ public final class HttpLoggingInterceptorTest { .assertLogEqual("Content-Encoding: gzip") .assertLogEqual("Content-Type: text/plain; charset=utf-8") .assertLogMatch("Content-Length: \\d+") - .assertLogEqual("<-- END HTTP (encoded body omitted)") + .assertLogEqual("") + .assertLogEqual("Hello, Hello, Hello") + .assertLogEqual("<-- END HTTP (19-byte, 29-gzipped-byte body)") .assertNoMoreLogs(); applicationLogs @@ -568,6 +574,43 @@ public final class HttpLoggingInterceptorTest { .assertNoMoreLogs(); } + @Test public void bodyResponseUnknownEncoded() throws IOException { + setLevel(Level.BODY); + + server.enqueue(new MockResponse() + // It's invalid to return this if not requested, but the server might anyway + .setHeader("Content-Encoding", "br") + .setHeader("Content-Type", PLAIN) + .setBody(new Buffer().write(ByteString.decodeBase64( + "iwmASGVsbG8sIEhlbGxvLCBIZWxsbwoD")))); + Response response = client.newCall(request().build()).execute(); + response.body().close(); + + networkLogs + .assertLogEqual("--> GET " + url + " http/1.1") + .assertLogEqual("Host: " + host) + .assertLogEqual("Connection: Keep-Alive") + .assertLogEqual("Accept-Encoding: gzip") + .assertLogMatch("User-Agent: okhttp/.+") + .assertLogEqual("--> END GET") + .assertLogMatch("<-- 200 OK " + url + " \\(\\d+ms\\)") + .assertLogEqual("Content-Encoding: br") + .assertLogEqual("Content-Type: text/plain; charset=utf-8") + .assertLogMatch("Content-Length: \\d+") + .assertLogEqual("<-- END HTTP (encoded body omitted)") + .assertNoMoreLogs(); + + applicationLogs + .assertLogEqual("--> GET " + url) + .assertLogEqual("--> END GET") + .assertLogMatch("<-- 200 OK " + url + " \\(\\d+ms\\)") + .assertLogEqual("Content-Encoding: br") + .assertLogEqual("Content-Type: text/plain; charset=utf-8") + .assertLogMatch("Content-Length: \\d+") + .assertLogEqual("<-- END HTTP (encoded body omitted)") + .assertNoMoreLogs(); + } + @Test public void bodyGetMalformedCharset() throws IOException { setLevel(Level.BODY);