From 0f44cb472afa8128359c6237720c75b841f0d691 Mon Sep 17 00:00:00 2001 From: Luis Costela Date: Fri, 21 Jul 2023 17:12:25 -0300 Subject: [PATCH] Added 'logbook.httpclient.decompress-response' flag --- README.md | 31 ++++++++++--------- .../LogbookHttpAsyncResponseConsumer.java | 15 ++++----- .../LogbookHttpResponseInterceptor.java | 18 ++++++----- .../logbook/httpclient/RemoteResponse.java | 3 +- .../LogbookHttpAsyncResponseConsumerTest.java | 4 +-- .../LogbookHttpInterceptorsGzipTest.java | 2 +- .../LogbookHttpInterceptorsPutTest.java | 2 +- .../LogbookHttpInterceptorsTest.java | 2 +- .../httpclient/RemoteResponseTest.java | 2 +- .../LogbookAutoConfiguration.java | 4 +-- ...itional-spring-configuration-metadata.json | 6 ++++ 11 files changed, 51 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index 0b5a7642c..6f3e8f201 100644 --- a/README.md +++ b/README.md @@ -934,21 +934,22 @@ MyClient(RestTemplateBuilder builder, LogbookClientHttpRequestInterceptor interc The following tables show the available configuration: -| Configuration | Description | Default | -|------------------------------------|------------------------------------------------------------------------------------------------------|-------------------------------| -| `logbook.include` | Include only certain URLs (if defined) | `[]` | -| `logbook.exclude` | Exclude certain URLs (overrides `logbook.include`) | `[]` | -| `logbook.filter.enabled` | Enable the [`LogbookFilter`](#servlet) | `true` | -| `logbook.filter.form-request-mode` | Determines how [form requests](#form-requests) are handled | `body` | -| `logbook.secure-filter.enabled` | Enable the [`SecureLogbookFilter`](#servlet) | `true` | -| `logbook.format.style` | [Formatting style](#formatting) (`http`, `json`, `curl` or `splunk`) | `json` | -| `logbook.strategy` | [Strategy](#strategy) (`default`, `status-at-least`, `body-only-if-status-at-least`, `without-body`) | `default` | -| `logbook.minimum-status` | Minimum status to enable logging (`status-at-least` and `body-only-if-status-at-least`) | `400` | -| `logbook.obfuscate.headers` | List of header names that need obfuscation | `[Authorization]` | -| `logbook.obfuscate.paths` | List of paths that need obfuscation. Check [Filtering](#filtering) for syntax. | `[]` | -| `logbook.obfuscate.parameters` | List of parameter names that need obfuscation | `[access_token]` | -| `logbook.write.chunk-size` | Splits log lines into smaller chunks of size up-to `chunk-size`. | `0` (disabled) | -| `logbook.write.max-body-size` | Truncates the body up to `max-body-size` and appends `...`. | `-1` (disabled) | +| Configuration | Description | Default | +|------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------| +| `logbook.include` | Include only certain URLs (if defined) | `[]` | +| `logbook.exclude` | Exclude certain URLs (overrides `logbook.include`) | `[]` | +| `logbook.filter.enabled` | Enable the [`LogbookFilter`](#servlet) | `true` | +| `logbook.filter.form-request-mode` | Determines how [form requests](#form-requests) are handled | `body` | +| `logbook.secure-filter.enabled` | Enable the [`SecureLogbookFilter`](#servlet) | `true` | +| `logbook.format.style` | [Formatting style](#formatting) (`http`, `json`, `curl` or `splunk`) | `json` | +| `logbook.strategy` | [Strategy](#strategy) (`default`, `status-at-least`, `body-only-if-status-at-least`, `without-body`) | `default` | +| `logbook.minimum-status` | Minimum status to enable logging (`status-at-least` and `body-only-if-status-at-least`) | `400` | +| `logbook.obfuscate.headers` | List of header names that need obfuscation | `[Authorization]` | +| `logbook.obfuscate.paths` | List of paths that need obfuscation. Check [Filtering](#filtering) for syntax. | `[]` | +| `logbook.obfuscate.parameters` | List of parameter names that need obfuscation | `[access_token]` | +| `logbook.write.chunk-size` | Splits log lines into smaller chunks of size up-to `chunk-size`. | `0` (disabled) | +| `logbook.write.max-body-size` | Truncates the body up to `max-body-size` and appends `...`. | `-1` (disabled) | +| `logbook.httpclient.decompress-response` | Enables/disables additional decompression process for HttpClient with gzip encoded body (to logging purposes only). This means extra decompression and possible performance impact. | `false` (disabled) | ##### Example configuration diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumer.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumer.java index fe7eafab4..c932e0f5e 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumer.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumer.java @@ -1,5 +1,9 @@ package org.zalando.logbook.httpclient; +import static org.apiguardian.api.API.Status.EXPERIMENTAL; + +import java.io.IOException; +import java.io.UncheckedIOException; import org.apache.http.HttpException; import org.apache.http.HttpResponse; import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; @@ -7,19 +11,16 @@ import org.apiguardian.api.API; import org.zalando.logbook.Logbook.ResponseProcessingStage; -import java.io.IOException; -import java.io.UncheckedIOException; - -import static org.apiguardian.api.API.Status.EXPERIMENTAL; - @API(status = EXPERIMENTAL) public final class LogbookHttpAsyncResponseConsumer extends ForwardingHttpAsyncResponseConsumer { private final HttpAsyncResponseConsumer consumer; + private final boolean decompressResponse; private HttpResponse response; - public LogbookHttpAsyncResponseConsumer(final HttpAsyncResponseConsumer consumer) { + public LogbookHttpAsyncResponseConsumer(HttpAsyncResponseConsumer consumer, boolean decompressResponse) { this.consumer = consumer; + this.decompressResponse = decompressResponse; } @Override @@ -38,7 +39,7 @@ public void responseCompleted(final HttpContext context) { final ResponseProcessingStage stage = find(context); try { - stage.process(new RemoteResponse(response)).write(); + stage.process(new RemoteResponse(response, decompressResponse)).write(); } catch (final IOException e) { throw new UncheckedIOException(e); } diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpResponseInterceptor.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpResponseInterceptor.java index defd45550..b8ff79f44 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpResponseInterceptor.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpResponseInterceptor.java @@ -1,5 +1,8 @@ package org.zalando.logbook.httpclient; +import static org.apiguardian.api.API.Status.STABLE; + +import java.io.IOException; import org.apache.http.HttpResponse; import org.apache.http.HttpResponseInterceptor; import org.apache.http.nio.client.HttpAsyncClient; @@ -7,13 +10,8 @@ import org.apiguardian.api.API; import org.zalando.logbook.Logbook.ResponseProcessingStage; -import java.io.IOException; - -import static org.apiguardian.api.API.Status.STABLE; - /** - * A response interceptor for synchronous responses. For {@link HttpAsyncClient} support, please use - * {@link LogbookHttpAsyncResponseConsumer} instead. + * A response interceptor for synchronous responses. For {@link HttpAsyncClient} support, please use {@link LogbookHttpAsyncResponseConsumer} instead. * * @see LogbookHttpRequestInterceptor * @see LogbookHttpAsyncResponseConsumer @@ -21,10 +19,16 @@ @API(status = STABLE) public final class LogbookHttpResponseInterceptor implements HttpResponseInterceptor { + private final boolean decompressResponse; + + public LogbookHttpResponseInterceptor(boolean decompressResponse) { + this.decompressResponse = decompressResponse; + } + @Override public void process(final HttpResponse original, final HttpContext context) throws IOException { final ResponseProcessingStage stage = find(context); - stage.process(new RemoteResponse(original)).write(); + stage.process(new RemoteResponse(original, decompressResponse)).write(); } private ResponseProcessingStage find(final HttpContext context) { diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/RemoteResponse.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/RemoteResponse.java index 19fcfda5b..07074242a 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/RemoteResponse.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/RemoteResponse.java @@ -32,6 +32,7 @@ final class RemoteResponse implements org.zalando.logbook.HttpResponse { private final AtomicReference state = new AtomicReference<>(new Unbuffered()); private final HttpResponse response; + private final boolean decompressResponse; private interface State { @@ -198,7 +199,7 @@ private static byte[] getDecompressedBytes(byte[] body) throws IOException { @Override public byte[] getBody() throws IOException { byte[] body = state.updateAndGet(throwingUnaryOperator(s -> s.buffer(response))).getBody(); - if (isGzip()) { + if (decompressResponse && isGzip()) { return getDecompressedBytes(body); } return body; diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumerTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumerTest.java index 0a9b3a46c..acf4a62a2 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumerTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpAsyncResponseConsumerTest.java @@ -79,12 +79,12 @@ protected HttpResponse sendAndReceive(@Nullable final String body) throws IOExce } return client.execute(create(request), - new LogbookHttpAsyncResponseConsumer<>(createConsumer()), callback).get(); + new LogbookHttpAsyncResponseConsumer<>(createConsumer(), false), callback).get(); } @Test void shouldWrapIOException() throws IOException { - final HttpAsyncResponseConsumer unit = new LogbookHttpAsyncResponseConsumer<>(createConsumer()); + final HttpAsyncResponseConsumer unit = new LogbookHttpAsyncResponseConsumer<>(createConsumer(), false); final BasicHttpContext context = new BasicHttpContext(); context.setAttribute(Attributes.STAGE, stage); diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsGzipTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsGzipTest.java index 8ef49e313..46968c010 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsGzipTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsGzipTest.java @@ -46,7 +46,7 @@ public final class LogbookHttpInterceptorsGzipTest extends AbstractHttpTest { private final CloseableHttpClient client = HttpClientBuilder.create() .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook)) - .addInterceptorFirst(new LogbookHttpResponseInterceptor()) + .addInterceptorFirst(new LogbookHttpResponseInterceptor(true)) .build(); @AfterEach diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsPutTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsPutTest.java index 97c32a8e8..4c300201d 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsPutTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsPutTest.java @@ -45,7 +45,7 @@ void defaultBehaviour() { private final CloseableHttpClient client = HttpClientBuilder.create() .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook)) - .addInterceptorFirst(new LogbookHttpResponseInterceptor()) + .addInterceptorFirst(new LogbookHttpResponseInterceptor(false)) .build(); @AfterEach diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsTest.java index 26dd552ee..96e3d2c21 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LogbookHttpInterceptorsTest.java @@ -29,7 +29,7 @@ public final class LogbookHttpInterceptorsTest extends AbstractHttpTest { private final CloseableHttpClient client = HttpClientBuilder.create() .addInterceptorFirst(new LogbookHttpRequestInterceptor(logbook)) - .addInterceptorFirst(new LogbookHttpResponseInterceptor()) + .addInterceptorFirst(new LogbookHttpResponseInterceptor(false)) .build(); @AfterEach diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/RemoteResponseTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/RemoteResponseTest.java index 246d432f1..9117f29a0 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/RemoteResponseTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/RemoteResponseTest.java @@ -19,7 +19,7 @@ final class RemoteResponseTest { private final BasicHttpEntity entity = new BasicHttpEntity(); private final HttpResponse delegate = new BasicHttpResponse(new ProtocolVersion("HTTP", 1, 1), 200, "OK"); - private final RemoteResponse unit = new RemoteResponse(delegate); + private final RemoteResponse unit = new RemoteResponse(delegate, false); @BeforeEach void setUpResponseBody() { diff --git a/logbook-spring-boot-autoconfigure/src/main/java/org/zalando/logbook/autoconfigure/LogbookAutoConfiguration.java b/logbook-spring-boot-autoconfigure/src/main/java/org/zalando/logbook/autoconfigure/LogbookAutoConfiguration.java index 3886b2543..c9f23002d 100644 --- a/logbook-spring-boot-autoconfigure/src/main/java/org/zalando/logbook/autoconfigure/LogbookAutoConfiguration.java +++ b/logbook-spring-boot-autoconfigure/src/main/java/org/zalando/logbook/autoconfigure/LogbookAutoConfiguration.java @@ -328,8 +328,8 @@ public LogbookHttpRequestInterceptor logbookHttpRequestInterceptor(final Logbook @Bean @ConditionalOnMissingBean(LogbookHttpResponseInterceptor.class) - public LogbookHttpResponseInterceptor logbookHttpResponseInterceptor() { - return new LogbookHttpResponseInterceptor(); + public LogbookHttpResponseInterceptor logbookHttpResponseInterceptor(@Value("${logbook.httpclient.decompress-response:false}") final boolean decompressResponse) { + return new LogbookHttpResponseInterceptor(decompressResponse); } } diff --git a/logbook-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json b/logbook-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json index f659b9bb9..45ae5aca1 100644 --- a/logbook-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json +++ b/logbook-spring-boot-autoconfigure/src/main/resources/META-INF/additional-spring-configuration-metadata.json @@ -65,6 +65,12 @@ "type": "java.lang.Integer", "defaultValue": 400, "description": "Minimum status code for conditional strategies." + }, + { + "name": "logbook.httpclient.decompress-response", + "type": "java.lang.Boolean", + "defaultValue": false, + "description": "Enables/disables additional decompression process for HttpClient with gzip encoded body (to logging purposes only)." } ] }