From b52d32ad678e17a245e43cc19adea6281a5c79ff Mon Sep 17 00:00:00 2001 From: Malte Pickhan Date: Tue, 9 Oct 2018 14:00:40 +0200 Subject: [PATCH 01/13] Drop LEVEL enum and replace with slf4j --- .../zalando/logbook/DefaultHttpLogWriter.java | 9 +++------ .../logbook/DefaultHttpLogWriterLevelTest.java | 18 +++++++----------- .../spring/LogbookAutoConfiguration.java | 2 +- .../logbook/spring/LogbookProperties.java | 2 +- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java index a26d9fec6..30a788064 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java @@ -3,6 +3,7 @@ import org.apiguardian.api.API; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; import java.util.function.BiConsumer; import java.util.function.Predicate; @@ -12,10 +13,6 @@ @API(status = STABLE) public final class DefaultHttpLogWriter implements HttpLogWriter { - public enum Level { - TRACE, DEBUG, INFO, WARN, ERROR - } - private final Logger logger; private final Predicate activator; private final BiConsumer consumer; @@ -30,8 +27,8 @@ public DefaultHttpLogWriter(final Logger logger) { public DefaultHttpLogWriter(final Logger logger, final Level level) { this.logger = logger; - this.activator = chooseActivator(level); - this.consumer = chooseConsumer(level); + activator = chooseActivator(level); + consumer = chooseConsumer(level); } private static Predicate chooseActivator(final Level level) { diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java index c0d14e657..abf05c31e 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java @@ -4,6 +4,7 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; import org.slf4j.Logger; +import org.slf4j.event.Level; import org.zalando.logbook.DefaultLogbook.SimpleCorrelation; import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; @@ -15,11 +16,6 @@ import static java.time.Duration.ZERO; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -import static org.zalando.logbook.DefaultHttpLogWriter.Level.DEBUG; -import static org.zalando.logbook.DefaultHttpLogWriter.Level.ERROR; -import static org.zalando.logbook.DefaultHttpLogWriter.Level.INFO; -import static org.zalando.logbook.DefaultHttpLogWriter.Level.TRACE; -import static org.zalando.logbook.DefaultHttpLogWriter.Level.WARN; public final class DefaultHttpLogWriterLevelTest { @@ -27,15 +23,15 @@ static Iterable data() { final Logger logger = mock(Logger.class); return Arrays.asList( - Arguments.of(create(logger, TRACE), logger, activator(Logger::isTraceEnabled), consumer(Logger::trace)), - Arguments.of(create(logger, DEBUG), logger, activator(Logger::isDebugEnabled), consumer(Logger::debug)), - Arguments.of(create(logger, INFO), logger, activator(Logger::isInfoEnabled), consumer(Logger::info)), - Arguments.of(create(logger, WARN), logger, activator(Logger::isWarnEnabled), consumer(Logger::warn)), - Arguments.of(create(logger, ERROR), logger, activator(Logger::isErrorEnabled), consumer(Logger::error)) + Arguments.of(create(logger, Level.TRACE), logger, activator(Logger::isTraceEnabled), consumer(Logger::trace)), + Arguments.of(create(logger, Level.DEBUG), logger, activator(Logger::isDebugEnabled), consumer(Logger::debug)), + Arguments.of(create(logger, Level.INFO), logger, activator(Logger::isInfoEnabled), consumer(Logger::info)), + Arguments.of(create(logger, Level.WARN), logger, activator(Logger::isWarnEnabled), consumer(Logger::warn)), + Arguments.of(create(logger, Level.ERROR), logger, activator(Logger::isErrorEnabled), consumer(Logger::error)) ); } - private static DefaultHttpLogWriter create(final Logger logger, final DefaultHttpLogWriter.Level trace) { + private static DefaultHttpLogWriter create(final Logger logger, final Level trace) { return new DefaultHttpLogWriter(logger, trace); } diff --git a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java index d887322b5..30e10b505 100644 --- a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java +++ b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java @@ -5,6 +5,7 @@ import org.apiguardian.api.API; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.slf4j.event.Level; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; @@ -25,7 +26,6 @@ import org.zalando.logbook.CurlHttpLogFormatter; import org.zalando.logbook.DefaultHttpLogFormatter; import org.zalando.logbook.DefaultHttpLogWriter; -import org.zalando.logbook.DefaultHttpLogWriter.Level; import org.zalando.logbook.HeaderFilter; import org.zalando.logbook.HeaderFilters; import org.zalando.logbook.HttpLogFormatter; diff --git a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookProperties.java b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookProperties.java index d3f7db3df..2acdcb42d 100644 --- a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookProperties.java +++ b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookProperties.java @@ -1,8 +1,8 @@ package org.zalando.logbook.spring; import org.apiguardian.api.API; +import org.slf4j.event.Level; import org.springframework.boot.context.properties.ConfigurationProperties; -import org.zalando.logbook.DefaultHttpLogWriter.Level; import org.zalando.logbook.Logbook; import java.util.ArrayList; From 668aca25ee32ca36bd87aa0f4b7e1225a6066ee2 Mon Sep 17 00:00:00 2001 From: Malte Pickhan Date: Tue, 9 Oct 2018 14:15:25 +0200 Subject: [PATCH 02/13] Re-add this accessor --- .../main/java/org/zalando/logbook/DefaultHttpLogWriter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java index 30a788064..124ead1cb 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java @@ -27,8 +27,8 @@ public DefaultHttpLogWriter(final Logger logger) { public DefaultHttpLogWriter(final Logger logger, final Level level) { this.logger = logger; - activator = chooseActivator(level); - consumer = chooseConsumer(level); + this.activator = chooseActivator(level); + this.consumer = chooseConsumer(level); } private static Predicate chooseActivator(final Level level) { From 52fdcf6f188000b7e6b2d30b1bf04311eb75d7db Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Wed, 31 Oct 2018 11:15:59 +0100 Subject: [PATCH 03/13] Work in progress --- README.md | 6 +- .../org/zalando/logbook/BaseHttpMessage.java | 66 -------- .../org/zalando/logbook/BaseHttpRequest.java | 37 ----- .../org/zalando/logbook/BaseHttpResponse.java | 12 -- .../org/zalando/logbook/BodyReplacer.java | 4 +- .../java/org/zalando/logbook/Correlation.java | 13 +- .../java/org/zalando/logbook/Correlator.java | 15 -- .../logbook/ForwardingBaseHttpMessage.java | 41 ----- .../logbook/ForwardingBaseHttpRequest.java | 55 ------- .../logbook/ForwardingBaseHttpResponse.java | 18 --- .../logbook/ForwardingHttpMessage.java | 31 +++- .../logbook/ForwardingHttpRequest.java | 55 ++++++- .../logbook/ForwardingHttpResponse.java | 19 ++- .../logbook/ForwardingRawHttpRequest.java | 25 --- .../logbook/ForwardingRawHttpResponse.java | 25 --- .../org/zalando/logbook/HttpLogFormatter.java | 5 +- .../org/zalando/logbook/HttpLogWriter.java | 7 +- .../java/org/zalando/logbook/HttpMessage.java | 58 ++++++- .../java/org/zalando/logbook/HttpRequest.java | 36 ++++- .../org/zalando/logbook/HttpResponse.java | 11 +- .../java/org/zalando/logbook/Logbook.java | 15 +- .../org/zalando/logbook/LogbookCreator.java | 22 +-- .../org/zalando/logbook/LogbookFactory.java | 9 +- .../org/zalando/logbook/Precorrelation.java | 6 +- .../org/zalando/logbook/RawHttpRequest.java | 18 --- .../org/zalando/logbook/RawHttpResponse.java | 18 --- .../org/zalando/logbook/RawRequestFilter.java | 22 --- .../zalando/logbook/RawResponseFilter.java | 22 --- .../java/org/zalando/logbook/RequestURI.java | 6 +- .../main/java/org/zalando/logbook/Sink.java | 17 ++ .../java/org/zalando/logbook/Strategy.java | 32 ++++ .../zalando/logbook/BaseHttpMessageTest.java | 64 -------- .../zalando/logbook/BaseHttpRequestTest.java | 26 --- .../zalando/logbook/EnforceCoverageTest.java | 8 +- .../org/zalando/logbook/ForwardingTest.java | 10 -- .../zalando/logbook/HttpLogWriterTest.java | 7 +- .../java/org/zalando/logbook/LogbookTest.java | 68 ++------ .../java/org/zalando/logbook/Mockbook.java | 80 ++-------- .../org/zalando/logbook/MockbookFactory.java | 14 +- .../zalando/logbook/RawHttpRequestTest.java | 19 --- .../zalando/logbook/RawHttpResponseTest.java | 19 --- .../zalando/logbook/RawRequestFilterTest.java | 20 --- .../logbook/RawResponseFilterTest.java | 20 --- .../org/zalando/logbook/RequestURITest.java | 2 +- .../logbook/BodyOnlyIfErrorStrategy.java | 37 +++++ .../logbook/BodyReplacementHttpRequest.java | 42 +++++ .../logbook/BodyReplacementHttpResponse.java | 42 +++++ .../BodyReplacementRawHttpRequest.java | 55 ------- .../BodyReplacementRawHttpResponse.java | 55 ------- .../org/zalando/logbook/BodyReplacers.java | 10 +- .../logbook/ChunkingHttpLogWriter.java | 26 +-- .../org/zalando/logbook/CompositeSink.java | 34 ++++ .../java/org/zalando/logbook/Conditions.java | 20 +-- .../zalando/logbook/CurlHttpLogFormatter.java | 8 +- .../logbook/DefaultHttpLogFormatter.java | 26 ++- .../zalando/logbook/DefaultHttpLogWriter.java | 10 +- .../org/zalando/logbook/DefaultLogbook.java | 148 ++++++------------ .../logbook/DefaultLogbookFactory.java | 31 ++-- .../java/org/zalando/logbook/DefaultSink.java | 35 +++++ .../org/zalando/logbook/DefaultStrategy.java | 8 + .../logbook/ErrorResponseOnlyStrategy.java | 35 +++++ .../org/zalando/logbook/HeaderFilters.java | 4 +- .../logbook/PreparedHttpLogFormatter.java | 32 ++-- ...equestFilters.java => RequestFilters.java} | 10 +- .../zalando/logbook/RequestOnlyStrategy.java | 15 ++ ...ponseFilters.java => ResponseFilters.java} | 10 +- .../zalando/logbook/ResponseOnlyStrategy.java | 18 +++ .../SomeRequestsWithoutBodyStrategy.java | 15 ++ .../zalando/logbook/StreamHttpLogWriter.java | 8 +- .../logbook/ChunkingHttpLogWriterTest.java | 37 ++--- .../org/zalando/logbook/ConditionsTest.java | 42 ++--- .../logbook/CurlHttpLogFormatterTest.java | 15 +- .../logbook/DefaultHttpLogFormatterTest.java | 13 +- .../DefaultHttpLogWriterLevelTest.java | 11 +- .../logbook/DefaultHttpLogWriterTest.java | 9 +- .../logbook/DefaultLogbookFactoryTest.java | 32 ---- .../zalando/logbook/DefaultLogbookTest.java | 89 ++++------- .../logbook/DelayedResponseLogWriter.java | 33 ---- .../logbook/DelayedResponseLogWriterTest.java | 31 ---- .../logbook/JsonHttpLogFormatterTest.java | 63 ++++---- .../logbook/RawRequestFiltersTest.java | 39 ----- .../logbook/RawResponseFiltersTest.java | 39 ----- .../zalando/logbook/RequestFiltersTest.java | 43 +++++ .../zalando/logbook/ResponseFiltersTest.java | 43 +++++ .../SeparateRequestResponseLogWriter.java | 34 ---- .../logbook/SplunkHttpLogFormatterTest.java | 36 ++--- .../logbook/StreamHttpLogWriterTest.java | 16 +- .../logbook/httpclient/Attributes.java | 2 +- .../logbook/httpclient/LocalRequest.java | 25 +-- .../LogbookHttpAsyncResponseConsumer.java | 21 ++- .../LogbookHttpRequestInterceptor.java | 15 +- .../LogbookHttpResponseInterceptor.java | 13 +- .../logbook/httpclient/RemoteResponse.java | 34 ++-- .../logbook/httpclient/AbstractHttpTest.java | 26 ++- .../logbook/httpclient/LocalRequestTest.java | 12 +- .../LogbookHttpAsyncResponseConsumerTest.java | 18 ++- .../LogbookHttpInterceptorsTest.java | 4 +- .../org/zalando/logbook/jaxrs/Attributes.java | 15 -- .../zalando/logbook/jaxrs/LocalRequest.java | 27 +++- .../zalando/logbook/jaxrs/LocalResponse.java | 26 ++- .../logbook/jaxrs/LogbookClientFilter.java | 30 ++-- .../logbook/jaxrs/LogbookServerFilter.java | 36 ++--- .../zalando/logbook/jaxrs/RemoteRequest.java | 18 ++- .../zalando/logbook/jaxrs/RemoteResponse.java | 17 +- .../logbook/jaxrs/TeeOutputStream.java | 4 + .../jaxrs/JerseyClientAndServerTest.java | 49 +++--- .../zalando/logbook/okhttp/LocalRequest.java | 29 ++-- .../logbook/okhttp/LogbookInterceptor.java | 10 +- .../logbook/okhttp/RemoteResponse.java | 30 ++-- .../okhttp/LogbookInterceptorTest.java | 31 ++-- .../logbook/servlet/LocalResponse.java | 47 ++---- .../logbook/servlet/NormalStrategy.java | 51 +++--- .../logbook/servlet/RemoteRequest.java | 42 ++--- .../logbook/servlet/SecurityStrategy.java | 24 ++- .../org/zalando/logbook/servlet/Strategy.java | 2 +- .../logbook/servlet/AsyncDispatchTest.java | 27 ++-- .../logbook/servlet/ErrorDispatchTest.java | 24 ++- .../logbook/servlet/FormattingTest.java | 27 ++-- .../servlet/ForwardingHttpLogFormatter.java | 10 +- .../logbook/servlet/LocalResponseTest.java | 10 +- .../servlet/MultiFilterSecurityTest.java | 99 +++--------- .../logbook/servlet/MultiFilterTest.java | 18 ++- .../org/zalando/logbook/servlet/SkipTest.java | 19 +-- .../org/zalando/logbook/servlet/TeeTest.java | 11 +- .../zalando/logbook/servlet/WritingTest.java | 42 +++-- .../spring/LogbookAutoConfiguration.java | 50 ++---- .../zalando/logbook/spring/ExcludeTest.java | 48 +++--- .../logbook/spring/FormatStyleCurlTest.java | 15 +- .../spring/FormatStyleDefaultTest.java | 15 +- .../logbook/spring/FormatStyleHttpTest.java | 15 +- .../logbook/spring/FormatStyleSplunkTest.java | 21 +-- .../zalando/logbook/spring/IncludeTest.java | 57 ++++--- .../spring/ObfuscateBodyCustomTest.java | 22 ++- .../spring/ObfuscateHeadersCustomTest.java | 22 ++- .../spring/ObfuscateHeadersDefaultTest.java | 21 ++- .../spring/ObfuscateParametersCustomTest.java | 21 ++- .../ObfuscateParametersDefaultTest.java | 21 ++- .../spring/ObfuscateRequestCustomTest.java | 20 +-- .../spring/ObfuscateResponseCustomTest.java | 25 ++- .../logbook/spring/WriteCustomTest.java | 10 +- .../logbook/spring/WriteLevelTest.java | 6 +- .../java/org/zalando/logbook/MockHeaders.java | 2 +- .../org/zalando/logbook/MockHttpRequest.java | 10 ++ .../org/zalando/logbook/MockHttpResponse.java | 10 ++ .../zalando/logbook/MockRawHttpRequest.java | 62 -------- .../zalando/logbook/MockRawHttpResponse.java | 49 ------ .../logbook/MockHttpMessageTester.java | 4 +- .../logbook/MockRawHttpRequestTest.java | 58 ------- .../logbook/MockRawHttpResponseTest.java | 51 ------ 149 files changed, 1595 insertions(+), 2427 deletions(-) delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/BaseHttpMessage.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/BaseHttpRequest.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/BaseHttpResponse.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/Correlator.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpMessage.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpRequest.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpResponse.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpRequest.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpResponse.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/RawHttpRequest.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/RawHttpResponse.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/RawRequestFilter.java delete mode 100644 logbook-api/src/main/java/org/zalando/logbook/RawResponseFilter.java create mode 100644 logbook-api/src/main/java/org/zalando/logbook/Sink.java create mode 100644 logbook-api/src/main/java/org/zalando/logbook/Strategy.java delete mode 100644 logbook-api/src/test/java/org/zalando/logbook/BaseHttpMessageTest.java delete mode 100644 logbook-api/src/test/java/org/zalando/logbook/BaseHttpRequestTest.java delete mode 100644 logbook-api/src/test/java/org/zalando/logbook/RawHttpRequestTest.java delete mode 100644 logbook-api/src/test/java/org/zalando/logbook/RawHttpResponseTest.java delete mode 100644 logbook-api/src/test/java/org/zalando/logbook/RawRequestFilterTest.java delete mode 100644 logbook-api/src/test/java/org/zalando/logbook/RawResponseFilterTest.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpRequest.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpResponse.java delete mode 100644 logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpRequest.java delete mode 100644 logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpResponse.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/DefaultSink.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/DefaultStrategy.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java rename logbook-core/src/main/java/org/zalando/logbook/{RawRequestFilters.java => RequestFilters.java} (64%) create mode 100644 logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java rename logbook-core/src/main/java/org/zalando/logbook/{RawResponseFilters.java => ResponseFilters.java} (63%) create mode 100644 logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriter.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriterTest.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/RawRequestFiltersTest.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/RawResponseFiltersTest.java create mode 100644 logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java create mode 100644 logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/SeparateRequestResponseLogWriter.java delete mode 100644 logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/Attributes.java delete mode 100644 logbook-test/src/main/java/org/zalando/logbook/MockRawHttpRequest.java delete mode 100644 logbook-test/src/main/java/org/zalando/logbook/MockRawHttpResponse.java delete mode 100644 logbook-test/src/test/java/org/zalando/logbook/MockRawHttpRequestTest.java delete mode 100644 logbook-test/src/test/java/org/zalando/logbook/MockRawHttpResponseTest.java diff --git a/README.md b/README.md index 516239dbc..e56c02aba 100644 --- a/README.md +++ b/README.md @@ -173,8 +173,6 @@ Logbook supports different types of filters: | Type | Operates on | Applies to | Default | |---------------------|--------------------------------|------------|---------------------------------------------------------------------------------------| -| `RawRequestFilter` | `RawHttpRequest` | request | binary/streams | -| `RawResponseFilter` | `RawHttpResponse` | response | binary/streams | | `QueryFilter` | Query string | request | `access_token` | | `HeaderFilter` | Header (single key-value pair) | both | `Authorization` | | `BodyFilter` | Content-Type and body | both | json -> `access_token` and `refresh_token`, form-url -> `client_secret` and `password`| @@ -184,7 +182,7 @@ Logbook supports different types of filters: `QueryFilter`, `HeaderFilter` and `BodyFilter` are relatively high-level and should cover all needs in ~90% of all cases. For more complicated setups one should fallback to the low-level variants, i.e. `RawRequestFilter` and `RawResponseFilter` as well as `RequestFilter` and `ResponseFilter` respectively (in conjunction with -`ForwardingRawHttpRequest`/`ForwardingRawHttpResponse` and `ForwardingHttpRequest`/`ForwardingHttpResponse`). +`ForwardingHttpRequest`/`ForwardingHttpResponse` and `ForwardingHttpRequest`/`ForwardingHttpResponse`). You can configure filters like this: @@ -518,7 +516,7 @@ Logbook comes with a convenient auto configuration for Spring Boot users. It set | `FilterRegistrationBean` | `unauthorizedLogbookFilter` | Based on `LogbookFilter` | | `FilterRegistrationBean` | `authorizedLogbookFilter` | Based on `LogbookFilter` | | `Logbook` | | Based on condition, filters, formatter and writer | -| `Predicate` | `requestCondition` | No filter; is later combined with `logbook.include` and logbook.exclude` | +| `Predicate` | `requestCondition` | No filter; is later combined with `logbook.exclude` and `logbook.exclude` | | `RawRequestFilter` | | `RawRequestFilters.defaultValue()` | | `RawResponseFilter` | | `RawResponseFilters.defaultValue()` | | `HeaderFilter` | | Based on `logbook.obfuscate.headers` | diff --git a/logbook-api/src/main/java/org/zalando/logbook/BaseHttpMessage.java b/logbook-api/src/main/java/org/zalando/logbook/BaseHttpMessage.java deleted file mode 100644 index adc4d347f..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/BaseHttpMessage.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import javax.annotation.Nullable; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import static java.lang.String.CASE_INSENSITIVE_ORDER; -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface BaseHttpMessage { - - String getProtocolVersion(); - - Origin getOrigin(); - - Map> getHeaders(); - - @Nullable - String getContentType(); - - Charset getCharset(); - - class HeadersBuilder { - - private Map> headers; - - public HeadersBuilder() { - // package private so we can trick code coverage - headers = new TreeMap<>(CASE_INSENSITIVE_ORDER); - } - - public HeadersBuilder put(final String key, final String value) { - final List values = headers.get(key); - if (values != null) { - values.add(value); - } else { - final ArrayList list = new ArrayList<>(); - list.add(value); - headers.put(key, list); - } - return this; - } - - public HeadersBuilder put(final String key, final Iterable values) { - for (final String value : values) { - put(key, value); - } - return this; - } - - public Map> build() { - for (final Map.Entry> e : headers.entrySet()) { - e.setValue(Collections.unmodifiableList(e.getValue())); - } - headers = Collections.unmodifiableMap(headers); - return headers; - } - } -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/BaseHttpRequest.java b/logbook-api/src/main/java/org/zalando/logbook/BaseHttpRequest.java deleted file mode 100644 index f3e7e88df..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/BaseHttpRequest.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.util.Optional; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface BaseHttpRequest extends BaseHttpMessage { - - String getRemote(); - - String getMethod(); - - /** - * Absolute Request URI including scheme, host, port (unless http/80 or https/443), path and query string. - * - *

Note that the URI may be invalid if the client issued an HTTP request using a malformed URL.

- * - * @return the requested URI - */ - default String getRequestUri() { - return RequestURI.reconstruct(this); - } - - String getScheme(); - - String getHost(); - - Optional getPort(); - - String getPath(); - - String getQuery(); - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/BaseHttpResponse.java b/logbook-api/src/main/java/org/zalando/logbook/BaseHttpResponse.java deleted file mode 100644 index 6bda961c8..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/BaseHttpResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface BaseHttpResponse extends BaseHttpMessage { - - int getStatus(); - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/BodyReplacer.java b/logbook-api/src/main/java/org/zalando/logbook/BodyReplacer.java index 05658c594..82de86e00 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/BodyReplacer.java +++ b/logbook-api/src/main/java/org/zalando/logbook/BodyReplacer.java @@ -10,13 +10,13 @@ @API(status = STABLE) @FunctionalInterface -public interface BodyReplacer { +public interface BodyReplacer { @Nullable String replace(final T message); @SafeVarargs - static BodyReplacer compound(final BodyReplacer... replacers) { + static BodyReplacer compound(final BodyReplacer... replacers) { return message -> Arrays.stream(replacers) .map(replacer -> replacer.replace(message)) .filter(Objects::nonNull) diff --git a/logbook-api/src/main/java/org/zalando/logbook/Correlation.java b/logbook-api/src/main/java/org/zalando/logbook/Correlation.java index 7ad290400..5600e1c61 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Correlation.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Correlation.java @@ -7,18 +7,15 @@ import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface Correlation { +public interface Correlation extends Precorrelation { String getId(); Duration getDuration(); - I getRequest(); - - O getResponse(); - - HttpRequest getOriginalRequest(); - - HttpResponse getOriginalResponse(); + @Override + default Correlation correlate() { + return this; + } } diff --git a/logbook-api/src/main/java/org/zalando/logbook/Correlator.java b/logbook-api/src/main/java/org/zalando/logbook/Correlator.java deleted file mode 100644 index e5c46f863..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/Correlator.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.io.IOException; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -@FunctionalInterface -public interface Correlator { - - void write(final RawHttpResponse response) throws IOException; - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpMessage.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpMessage.java deleted file mode 100644 index 10e5c95bb..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpMessage.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface ForwardingBaseHttpMessage extends BaseHttpMessage { - - BaseHttpMessage delegate(); - - @Override - default String getProtocolVersion() { - return delegate().getProtocolVersion(); - } - - @Override - default Origin getOrigin() { - return delegate().getOrigin(); - } - - @Override - default Map> getHeaders() { - return delegate().getHeaders(); - } - - @Override - default String getContentType() { - return delegate().getContentType(); - } - - @Override - default Charset getCharset() { - return delegate().getCharset(); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpRequest.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpRequest.java deleted file mode 100644 index 64c29e054..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.util.Optional; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface ForwardingBaseHttpRequest extends ForwardingBaseHttpMessage, BaseHttpRequest { - - @Override - BaseHttpRequest delegate(); - - @Override - default String getRemote() { - return delegate().getRemote(); - } - - @Override - default String getMethod() { - return delegate().getMethod(); - } - - @Override - default String getRequestUri() { - return delegate().getRequestUri(); - } - - @Override - default String getScheme() { - return delegate().getScheme(); - } - - @Override - default String getHost() { - return delegate().getHost(); - } - - @Override - default Optional getPort() { - return delegate().getPort(); - } - - @Override - default String getPath() { - return delegate().getPath(); - } - - @Override - default String getQuery() { - return delegate().getQuery(); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpResponse.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpResponse.java deleted file mode 100644 index 8b7420b41..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingBaseHttpResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface ForwardingBaseHttpResponse extends ForwardingBaseHttpMessage, BaseHttpResponse { - - @Override - BaseHttpResponse delegate(); - - @Override - default int getStatus() { - return delegate().getStatus(); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpMessage.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpMessage.java index 71818bebf..e357e1bde 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpMessage.java +++ b/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpMessage.java @@ -3,15 +3,42 @@ import org.apiguardian.api.API; import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface ForwardingHttpMessage extends ForwardingBaseHttpMessage, HttpMessage { +public interface ForwardingHttpMessage extends HttpMessage { - @Override HttpMessage delegate(); + @Override + default String getProtocolVersion() { + return delegate().getProtocolVersion(); + } + + @Override + default Origin getOrigin() { + return delegate().getOrigin(); + } + + @Override + default Map> getHeaders() { + return delegate().getHeaders(); + } + + @Override + default String getContentType() { + return delegate().getContentType(); + } + + @Override + default Charset getCharset() { + return delegate().getCharset(); + } + @Override default byte[] getBody() throws IOException { return delegate().getBody(); diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpRequest.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpRequest.java index 0799df6cf..ac0880aca 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpRequest.java +++ b/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpRequest.java @@ -2,12 +2,65 @@ import org.apiguardian.api.API; +import java.io.IOException; +import java.util.Optional; + import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface ForwardingHttpRequest extends ForwardingHttpMessage, ForwardingBaseHttpRequest, HttpRequest { +public interface ForwardingHttpRequest extends ForwardingHttpMessage, HttpRequest { @Override HttpRequest delegate(); + @Override + default String getRemote() { + return delegate().getRemote(); + } + + @Override + default String getMethod() { + return delegate().getMethod(); + } + + @Override + default String getRequestUri() { + return delegate().getRequestUri(); + } + + @Override + default String getScheme() { + return delegate().getScheme(); + } + + @Override + default String getHost() { + return delegate().getHost(); + } + + @Override + default Optional getPort() { + return delegate().getPort(); + } + + @Override + default String getPath() { + return delegate().getPath(); + } + + @Override + default String getQuery() { + return delegate().getQuery(); + } + + @Override + default HttpRequest withBody() throws IOException { + return delegate().withBody(); + } + + @Override + default HttpRequest withoutBody() { + return delegate().withoutBody(); + } + } diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpResponse.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpResponse.java index ee7d76cc2..bfb437101 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpResponse.java +++ b/logbook-api/src/main/java/org/zalando/logbook/ForwardingHttpResponse.java @@ -2,12 +2,29 @@ import org.apiguardian.api.API; +import java.io.IOException; + import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface ForwardingHttpResponse extends ForwardingHttpMessage, ForwardingBaseHttpResponse, HttpResponse { +public interface ForwardingHttpResponse extends ForwardingHttpMessage, HttpResponse { @Override HttpResponse delegate(); + @Override + default int getStatus() { + return delegate().getStatus(); + } + + @Override + default HttpResponse withBody() throws IOException { + return delegate().withBody(); + } + + @Override + default HttpResponse withoutBody() { + return delegate().withoutBody(); + } + } diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpRequest.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpRequest.java deleted file mode 100644 index 3c1d500fd..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpRequest.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.io.IOException; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface ForwardingRawHttpRequest extends ForwardingBaseHttpRequest, RawHttpRequest { - - @Override - RawHttpRequest delegate(); - - @Override - default HttpRequest withBody() throws IOException { - return delegate().withBody(); - } - - @Override - default void withoutBody() throws IOException { - delegate().withoutBody(); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpResponse.java b/logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpResponse.java deleted file mode 100644 index 77b17d9f4..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/ForwardingRawHttpResponse.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.io.IOException; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface ForwardingRawHttpResponse extends ForwardingBaseHttpResponse, RawHttpResponse { - - @Override - RawHttpResponse delegate(); - - @Override - default HttpResponse withBody() throws IOException { - return delegate().withBody(); - } - - @Override - default void withoutBody() throws IOException { - delegate().withoutBody(); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpLogFormatter.java b/logbook-api/src/main/java/org/zalando/logbook/HttpLogFormatter.java index d7203420a..fee105cdb 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpLogFormatter.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpLogFormatter.java @@ -9,8 +9,7 @@ @API(status = STABLE) public interface HttpLogFormatter { - String format(Precorrelation precorrelation) throws IOException; - - String format(Correlation correlation) throws IOException; + String format(Precorrelation precorrelation, HttpRequest request) throws IOException; + String format(Correlation correlation, HttpResponse response) throws IOException; } diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpLogWriter.java b/logbook-api/src/main/java/org/zalando/logbook/HttpLogWriter.java index 2f3bfc11e..02a14cf20 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpLogWriter.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpLogWriter.java @@ -9,12 +9,11 @@ @API(status = STABLE) public interface HttpLogWriter { - default boolean isActive(final RawHttpRequest request) throws IOException { + default boolean isActive() { return true; } - void writeRequest(Precorrelation precorrelation) throws IOException; - - void writeResponse(Correlation correlation) throws IOException; + void write(Precorrelation precorrelation, String request) throws IOException; + void write(Correlation correlation, String response) throws IOException; } diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java b/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java index 29508dd20..303341bd3 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java @@ -2,12 +2,68 @@ import org.apiguardian.api.API; +import javax.annotation.Nullable; import java.io.IOException; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; +import static java.lang.String.CASE_INSENSITIVE_ORDER; import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface HttpMessage extends BaseHttpMessage { +public interface HttpMessage { + + String getProtocolVersion(); + + Origin getOrigin(); + + Map> getHeaders(); + + @Nullable + String getContentType(); + + Charset getCharset(); + + class HeadersBuilder { + + private Map> headers; + + public HeadersBuilder() { + // package private so we can trick code coverage + headers = new TreeMap<>(CASE_INSENSITIVE_ORDER); + } + + public HeadersBuilder put(final String key, final String value) { + final List values = headers.get(key); + if (values != null) { + values.add(value); + } else { + final ArrayList list = new ArrayList<>(); + list.add(value); + headers.put(key, list); + } + return this; + } + + public HeadersBuilder put(final String key, final Iterable values) { + for (final String value : values) { + put(key, value); + } + return this; + } + + public Map> build() { + for (final Map.Entry> e : headers.entrySet()) { + e.setValue(Collections.unmodifiableList(e.getValue())); + } + headers = Collections.unmodifiableMap(headers); + return headers; + } + } byte[] getBody() throws IOException; diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpRequest.java b/logbook-api/src/main/java/org/zalando/logbook/HttpRequest.java index 9205321c2..45ece9237 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpRequest.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpRequest.java @@ -2,9 +2,43 @@ import org.apiguardian.api.API; +import java.io.IOException; +import java.util.Optional; + import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface HttpRequest extends HttpMessage, BaseHttpRequest { +public interface HttpRequest extends HttpMessage { + + String getRemote(); + + String getMethod(); + + /** + * Absolute Request URI including scheme, host, port (unless http/80 or https/443), path and query string. + * + *

Note that the URI may be invalid if the client issued an HTTP request using a malformed URL.

+ * + * @return the requested URI + */ + default String getRequestUri() { + return RequestURI.reconstruct(this); + } + + String getScheme(); + + String getHost(); + + Optional getPort(); + + String getPath(); + + String getQuery(); + + // TODO void vs pseudo-function (mutable) + HttpRequest withBody() throws IOException; + + // TODO require all implementations to be without-body by default + HttpRequest withoutBody(); } diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpResponse.java b/logbook-api/src/main/java/org/zalando/logbook/HttpResponse.java index 8e4749edf..5685fc716 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpResponse.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpResponse.java @@ -2,9 +2,18 @@ import org.apiguardian.api.API; +import java.io.IOException; + import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface HttpResponse extends HttpMessage, BaseHttpResponse { +public interface HttpResponse extends HttpMessage { + + int getStatus(); + + // TODO void vs pseudo-function (mutable) + HttpResponse withBody() throws IOException; + + HttpResponse withoutBody(); } diff --git a/logbook-api/src/main/java/org/zalando/logbook/Logbook.java b/logbook-api/src/main/java/org/zalando/logbook/Logbook.java index 306efde57..ab86e04d2 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Logbook.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Logbook.java @@ -3,14 +3,25 @@ import org.apiguardian.api.API; import java.io.IOException; -import java.util.Optional; import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) public interface Logbook { - Optional write(final RawHttpRequest request) throws IOException; + RequestWritingStage process(HttpRequest request) throws IOException; + + interface RequestWritingStage { + ResponseProcessingStage write() throws IOException; + } + + interface ResponseProcessingStage { + ResponseWritingStage process(HttpResponse response) throws IOException; + } + + interface ResponseWritingStage { + void write() throws IOException; + } static Logbook create() { return builder().build(); diff --git a/logbook-api/src/main/java/org/zalando/logbook/LogbookCreator.java b/logbook-api/src/main/java/org/zalando/logbook/LogbookCreator.java index 2921ceb35..99680bf64 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/LogbookCreator.java +++ b/logbook-api/src/main/java/org/zalando/logbook/LogbookCreator.java @@ -25,24 +25,14 @@ public static final class Builder { @lombok.Builder(builderClassName = "Builder") private static Logbook create( - @Nullable final Predicate condition, - @Singular final List rawRequestFilters, - @Singular final List rawResponseFilters, + @Nullable final Predicate condition, @Singular final List queryFilters, @Singular final List headerFilters, @Singular final List bodyFilters, @Singular final List requestFilters, @Singular final List responseFilters, - @Nullable final HttpLogFormatter formatter, - @Nullable final HttpLogWriter writer) { - - @Nullable final RawRequestFilter rawRequestFilter = rawRequestFilters.stream() - .reduce(RawRequestFilter::merge) - .orElse(null); - - @Nullable final RawResponseFilter rawResponseFilter = rawResponseFilters.stream() - .reduce(RawResponseFilter::merge) - .orElse(null); + @Nullable final Strategy strategy, + @Nullable final Sink sink) { @Nullable final QueryFilter queryFilter = queryFilters.stream() .reduce(QueryFilter::merge) @@ -68,15 +58,13 @@ private static Logbook create( return factory.create( condition, - rawRequestFilter, - rawResponseFilter, queryFilter, headerFilter, bodyFilter, requestFilter, responseFilter, - formatter, - writer); + strategy, + sink); } } diff --git a/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java b/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java index 64f4971ea..610efe721 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java +++ b/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java @@ -3,6 +3,7 @@ import org.apiguardian.api.API; import javax.annotation.Nullable; + import java.util.function.Predicate; import static java.util.ServiceLoader.load; @@ -14,15 +15,13 @@ interface LogbookFactory { LogbookFactory INSTANCE = load(LogbookFactory.class).iterator().next(); Logbook create( - @Nullable final Predicate condition, - @Nullable final RawRequestFilter rawRequestFilter, - @Nullable final RawResponseFilter rawResponseFilter, + @Nullable final Predicate condition, @Nullable final QueryFilter queryFilter, @Nullable final HeaderFilter headerFilter, @Nullable final BodyFilter bodyFilter, @Nullable final RequestFilter requestFilter, @Nullable final ResponseFilter responseFilter, - @Nullable final HttpLogFormatter formatter, - @Nullable final HttpLogWriter writer); + @Nullable final Strategy strategy, + @Nullable final Sink sink); } diff --git a/logbook-api/src/main/java/org/zalando/logbook/Precorrelation.java b/logbook-api/src/main/java/org/zalando/logbook/Precorrelation.java index 882598b00..d41948e41 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Precorrelation.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Precorrelation.java @@ -5,12 +5,10 @@ import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public interface Precorrelation { +public interface Precorrelation { String getId(); - I getRequest(); - - HttpRequest getOriginalRequest(); + Correlation correlate(); } diff --git a/logbook-api/src/main/java/org/zalando/logbook/RawHttpRequest.java b/logbook-api/src/main/java/org/zalando/logbook/RawHttpRequest.java deleted file mode 100644 index be2c4f1fd..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/RawHttpRequest.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.io.IOException; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface RawHttpRequest extends BaseHttpRequest { - - HttpRequest withBody() throws IOException; - - default void withoutBody() throws IOException { - // omitting the body is the default behavior, unless withBody has been called - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/RawHttpResponse.java b/logbook-api/src/main/java/org/zalando/logbook/RawHttpResponse.java deleted file mode 100644 index 4992a37f0..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/RawHttpResponse.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import java.io.IOException; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -public interface RawHttpResponse extends BaseHttpResponse { - - HttpResponse withBody() throws IOException; - - default void withoutBody() throws IOException { - // omitting the body is the default behavior, unless withBody has been called - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/RawRequestFilter.java b/logbook-api/src/main/java/org/zalando/logbook/RawRequestFilter.java deleted file mode 100644 index aa2d298b8..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/RawRequestFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -@FunctionalInterface -public interface RawRequestFilter { - - RawHttpRequest filter(final RawHttpRequest request); - - static RawRequestFilter none() { - return request -> request; - } - - static RawRequestFilter merge(final RawRequestFilter left, final RawRequestFilter right) { - return request -> - left.filter(right.filter(request)); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/RawResponseFilter.java b/logbook-api/src/main/java/org/zalando/logbook/RawResponseFilter.java deleted file mode 100644 index dd88e6f11..000000000 --- a/logbook-api/src/main/java/org/zalando/logbook/RawResponseFilter.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.zalando.logbook; - -import org.apiguardian.api.API; - -import static org.apiguardian.api.API.Status.STABLE; - -@API(status = STABLE) -@FunctionalInterface -public interface RawResponseFilter { - - RawHttpResponse filter(final RawHttpResponse response); - - static RawResponseFilter none() { - return response -> response; - } - - static RawResponseFilter merge(final RawResponseFilter left, final RawResponseFilter right) { - return response -> - left.filter(right.filter(response)); - } - -} diff --git a/logbook-api/src/main/java/org/zalando/logbook/RequestURI.java b/logbook-api/src/main/java/org/zalando/logbook/RequestURI.java index 3e93c0799..cbfe1c9a6 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/RequestURI.java +++ b/logbook-api/src/main/java/org/zalando/logbook/RequestURI.java @@ -20,15 +20,15 @@ enum Component { SCHEME, AUTHORITY, PATH, QUERY } - static String reconstruct(final BaseHttpRequest request) { + static String reconstruct(final HttpRequest request) { return reconstruct(request, EnumSet.allOf(Component.class)); } - static String reconstruct(final BaseHttpRequest request, final Component... components) { + static String reconstruct(final HttpRequest request, final Component... components) { return reconstruct(request, EnumSet.copyOf(asList(components))); } - private static String reconstruct(final BaseHttpRequest request, final Set components) { + private static String reconstruct(final HttpRequest request, final Set components) { final String scheme = request.getScheme(); final String host = request.getHost(); final Optional port = request.getPort(); diff --git a/logbook-api/src/main/java/org/zalando/logbook/Sink.java b/logbook-api/src/main/java/org/zalando/logbook/Sink.java new file mode 100644 index 000000000..3c6c8028e --- /dev/null +++ b/logbook-api/src/main/java/org/zalando/logbook/Sink.java @@ -0,0 +1,17 @@ +package org.zalando.logbook; + +import java.io.IOException; + +public interface Sink { + + default boolean isActive() { + return true; + } + + void write(Precorrelation precorrelation, HttpRequest request) throws IOException; + void write(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException; + + // TODO needed?! + void writeBoth(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException; + +} diff --git a/logbook-api/src/main/java/org/zalando/logbook/Strategy.java b/logbook-api/src/main/java/org/zalando/logbook/Strategy.java new file mode 100644 index 000000000..755cd7ffc --- /dev/null +++ b/logbook-api/src/main/java/org/zalando/logbook/Strategy.java @@ -0,0 +1,32 @@ +package org.zalando.logbook; + +import org.apiguardian.api.API; + +import java.io.IOException; + +import static org.apiguardian.api.API.Status.STABLE; + +@API(status = STABLE) +public interface Strategy { + + // TODO default methods vs. DefaultStrategy + + default HttpRequest process(final HttpRequest request) throws IOException { + return request.withBody(); + } + + default void write(final Precorrelation precorrelation, final HttpRequest request, + final Sink sink) throws IOException { + sink.write(precorrelation, request); + } + + default HttpResponse process(HttpRequest request, final HttpResponse response) throws IOException { + return response.withBody(); + } + + default void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, + final Sink sink) throws IOException { + sink.write(correlation, request, response); + } + +} diff --git a/logbook-api/src/test/java/org/zalando/logbook/BaseHttpMessageTest.java b/logbook-api/src/test/java/org/zalando/logbook/BaseHttpMessageTest.java deleted file mode 100644 index 235c9b189..000000000 --- a/logbook-api/src/test/java/org/zalando/logbook/BaseHttpMessageTest.java +++ /dev/null @@ -1,64 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasItem; -import static org.hamcrest.Matchers.hasItems; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public final class BaseHttpMessageTest { - - @Test - void shouldUseCaseInsensitiveHeaders() { - final Map> headers = new BaseHttpMessage.HeadersBuilder() - .put("X-Secret", "s3cr3t") - .put("X-Secret", "knowledge") - .put("Y-Secret", Arrays.asList("one", "two")) - .build(); - - assertThat(headers.get("x-secret"), hasItem("s3cr3t")); - assertThat(headers.get("x-secret"), hasItem("knowledge")); - assertThat(headers.get("Y-SECRET"), hasItem("one")); - assertThat(headers.get("Y-SECRET"), hasItem("two")); - } - - @Test - void shouldBuildImmutableHeaders() { - final Map> headers = new BaseHttpMessage.HeadersBuilder() - .put("a", "b") - .put("a", "c") - .put("d", Arrays.asList("e", "f")) - .build(); - - assertThrows(UnsupportedOperationException.class, () -> - headers.put("x", Arrays.asList("y", "z"))); - - final List a = headers.get("a"); - assertNotNull(a); - assertThat(a, hasItems("b", "c")); - - assertThrows(UnsupportedOperationException.class, () -> - a.add("x")); - } - - @Test - void shouldRefuseUpdateHeadersAfterBuild() { - final BaseHttpMessage.HeadersBuilder builder = new BaseHttpMessage.HeadersBuilder(); - builder.put("a", "b").build(); - - assertThrows(UnsupportedOperationException.class, () -> - // existing key - builder.put("a", "b")); - - assertThrows(UnsupportedOperationException.class, () -> - // new key - builder.put("x", "y")); - } - -} diff --git a/logbook-api/src/test/java/org/zalando/logbook/BaseHttpRequestTest.java b/logbook-api/src/test/java/org/zalando/logbook/BaseHttpRequestTest.java deleted file mode 100644 index 11b1bfa12..000000000 --- a/logbook-api/src/test/java/org/zalando/logbook/BaseHttpRequestTest.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.util.Optional; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; - -public final class BaseHttpRequestTest { - - @Test - void shouldReconstructURI() { - final BaseHttpRequest unit = spy(BaseHttpRequest.class); - when(unit.getScheme()).thenReturn("http"); - when(unit.getHost()).thenReturn("localhost"); - when(unit.getPort()).thenReturn(Optional.empty()); - when(unit.getPath()).thenReturn("/test"); - when(unit.getQuery()).thenReturn("limit=1"); - - assertThat(unit.getRequestUri(), is("http://localhost/test?limit=1")); - } - -} diff --git a/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java b/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java index 9eb48d810..73300275b 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java @@ -15,24 +15,18 @@ public final class EnforceCoverageTest { void shouldCoverUselessClearMethods() { final LogbookCreator.Builder builder = Logbook.builder(); - builder.clearRawRequestFilters(); - builder.clearRawResponseFilters(); builder.clearQueryFilters(); builder.clearHeaderFilters(); builder.clearBodyFilters(); builder.clearRequestFilters(); builder.clearResponseFilters(); - builder.rawRequestFilter(mock(RawRequestFilter.class)); - builder.rawResponseFilter(mock(RawResponseFilter.class)); builder.queryFilter(mock(QueryFilter.class)); builder.headerFilter(mock(HeaderFilter.class)); builder.bodyFilter(mock(BodyFilter.class)); builder.requestFilter(mock(RequestFilter.class)); builder.responseFilter(mock(ResponseFilter.class)); - builder.clearRawRequestFilters(); - builder.clearRawResponseFilters(); builder.clearQueryFilters(); builder.clearHeaderFilters(); builder.clearBodyFilters(); @@ -43,6 +37,6 @@ void shouldCoverUselessClearMethods() { @Test void fakeLogbookShouldThrow() { assertThrows(UnsupportedOperationException.class, () -> - Logbook.create().write(mock(RawHttpRequest.class))); + Logbook.create().process(mock(HttpRequest.class))); } } diff --git a/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java b/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java index b8d7dd7eb..e65257434 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java @@ -11,16 +11,6 @@ public final class ForwardingTest { - @Test - void shouldForwardRawHttpRequests() { - test(RawHttpRequest.class, request -> (ForwardingRawHttpRequest) () -> request); - } - - @Test - void shouldForwardRawHttpResponses() { - test(RawHttpResponse.class, response -> (ForwardingRawHttpResponse) () -> response); - } - @Test void shouldForwardHttpRequests() { test(HttpRequest.class, request -> (ForwardingHttpRequest) () -> request); diff --git a/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java b/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java index 6bfcbf634..91d9c9aec 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java @@ -2,20 +2,17 @@ import org.junit.jupiter.api.Test; -import java.io.IOException; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; public final class HttpLogWriterTest { @Test - void shouldBeActiveByDefault() throws IOException { + void shouldBeActiveByDefault() { final HttpLogWriter unit = spy(HttpLogWriter.class); - assertThat(unit.isActive(mock(RawHttpRequest.class)), is(true)); + assertThat(unit.isActive(), is(true)); } } diff --git a/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java b/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java index 877799f65..9ed3d79bc 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java @@ -4,8 +4,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import java.util.function.Predicate; - import static java.util.Arrays.asList; import static java.util.Collections.singleton; import static java.util.Collections.singletonList; @@ -22,17 +20,13 @@ public class LogbookTest { - @SuppressWarnings("unchecked") - private final Predicate predicate = mock(Predicate.class); - private final RawRequestFilter rawRequestFilter = mock(RawRequestFilter.class); - private final RawResponseFilter rawResponseFilter = mock(RawResponseFilter.class); private final HeaderFilter headerFilter = mock(HeaderFilter.class); private final QueryFilter queryFilter = mock(QueryFilter.class); private final BodyFilter bodyFilter = mock(BodyFilter.class); private final RequestFilter requestFilter = mock(RequestFilter.class); private final ResponseFilter responseFilter = mock(ResponseFilter.class); - private final HttpLogFormatter formatter = mock(HttpLogFormatter.class); - private final HttpLogWriter writer = mock(HttpLogWriter.class); + private final Strategy strategy = mock(Strategy.class); + private final Sink sink = mock(Sink.class); private Mockbook setUp(final int times) { return (Mockbook) create(times); @@ -43,30 +37,21 @@ private Logbook create(final int times) { switch (times) { case 0: return Logbook.builder() - .condition(predicate) - .formatter(formatter) - .writer(writer) + .strategy(strategy) + .sink(sink) .build(); case 1: return Logbook.builder() - .condition(predicate) - .rawRequestFilter(rawRequestFilter) - .rawResponseFilter(rawResponseFilter) .queryFilter(queryFilter) .headerFilter(headerFilter) .bodyFilter(bodyFilter) .requestFilter(requestFilter) .responseFilter(responseFilter) - .formatter(formatter) - .writer(writer) + .strategy(strategy) + .sink(sink) .build(); case 2: return Logbook.builder() - .condition(predicate) - .rawRequestFilter(rawRequestFilter) - .rawRequestFilter(rawRequestFilter) - .rawResponseFilter(rawResponseFilter) - .rawResponseFilter(rawResponseFilter) .queryFilter(queryFilter) .queryFilter(queryFilter) .headerFilter(headerFilter) @@ -77,16 +62,11 @@ private Logbook create(final int times) { .requestFilter(requestFilter) .responseFilter(responseFilter) .responseFilter(responseFilter) - .formatter(formatter) - .writer(writer) + .strategy(strategy) + .sink(sink) .build(); case 3: return Logbook.builder() - .condition(predicate) - .rawRequestFilters(singleton(rawRequestFilter)) - .rawRequestFilters(asList(rawRequestFilter, rawRequestFilter)) - .rawResponseFilters(singleton(rawResponseFilter)) - .rawResponseFilters(asList(rawResponseFilter, rawResponseFilter)) .queryFilters(singleton(queryFilter)) .queryFilters(asList(queryFilter, queryFilter)) .headerFilters(singleton(headerFilter)) @@ -97,8 +77,8 @@ private Logbook create(final int times) { .requestFilters(asList(requestFilter, requestFilter)) .responseFilters(singleton(responseFilter)) .responseFilters(asList(responseFilter, responseFilter)) - .formatter(formatter) - .writer(writer) + .strategy(strategy) + .sink(sink) .build(); default: throw new UnsupportedOperationException(); @@ -111,34 +91,6 @@ void shouldCreateInstance() { assertThat(logbook, is(notNullValue())); } - @Test - void shouldNotCombineRawRequestFilters() { - final Mockbook unit = setUp(0); - assertThat(unit.getRawRequestFilter(), is(nullValue())); - } - - @ParameterizedTest - @ValueSource(ints = {1, 2, 3}) - void shouldCombineRawRequestFilters(final int times) { - final Mockbook unit = setUp(times); - unit.getRawRequestFilter().filter(mock(RawHttpRequest.class)); - verify(rawRequestFilter, times(times)).filter(any()); - } - - @Test - void shouldNotCombineRawResponseFilters() { - final Mockbook unit = setUp(0); - assertThat(unit.getRawResponseFilter(), is(nullValue())); - } - - @ParameterizedTest - @ValueSource(ints = {1, 2, 3}) - void shouldCombineRawResponseFilters(final int times) { - final Mockbook unit = setUp(times); - unit.getRawResponseFilter().filter(mock(RawHttpResponse.class)); - verify(rawResponseFilter, times(times)).filter(any()); - } - @Test void shouldNotCombineQueryFilters() { final Mockbook unit = setUp(0); diff --git a/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java b/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java index e88110229..5c413bc33 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java +++ b/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java @@ -1,88 +1,26 @@ package org.zalando.logbook; -import javax.annotation.Nullable; -import java.util.Optional; +import lombok.AllArgsConstructor; +import lombok.Getter; + import java.util.function.Predicate; +@AllArgsConstructor +@Getter final class Mockbook implements Logbook { - private final Predicate predicate; - private final RawRequestFilter rawRequestFilter; - private final RawResponseFilter rawResponseFilter; + private final Predicate predicate; private final QueryFilter queryFilter; private final HeaderFilter headerFilter; private final BodyFilter bodyFilter; private final RequestFilter requestFilter; private final ResponseFilter responseFilter; - private final HttpLogFormatter formatter; - private final HttpLogWriter writer; - - public Mockbook( - @Nullable final Predicate predicate, - @Nullable final RawRequestFilter rawRequestFilter, - @Nullable final RawResponseFilter rawResponseFilter, - @Nullable final QueryFilter queryFilter, - @Nullable final HeaderFilter headerFilter, - @Nullable final BodyFilter bodyFilter, - @Nullable final RequestFilter requestFilter, - @Nullable final ResponseFilter responseFilter, - @Nullable final HttpLogFormatter formatter, - @Nullable final HttpLogWriter writer) { - this.predicate = predicate; - this.rawRequestFilter = rawRequestFilter; - this.rawResponseFilter = rawResponseFilter; - this.queryFilter = queryFilter; - this.headerFilter = headerFilter; - this.bodyFilter = bodyFilter; - this.requestFilter = requestFilter; - this.responseFilter = responseFilter; - this.formatter = formatter; - this.writer = writer; - } + private final Strategy strategy; + private final Sink sink; @Override - public Optional write(final RawHttpRequest request) { + public RequestWritingStage process(final HttpRequest request) { throw new UnsupportedOperationException(); } - public Predicate getPredicate() { - return predicate; - } - - public RawRequestFilter getRawRequestFilter() { - return rawRequestFilter; - } - - public RawResponseFilter getRawResponseFilter() { - return rawResponseFilter; - } - - public BodyFilter getBodyFilter() { - return bodyFilter; - } - - public HeaderFilter getHeaderFilter() { - return headerFilter; - } - - public QueryFilter getQueryFilter() { - return queryFilter; - } - - public RequestFilter getRequestFilter() { - return requestFilter; - } - - public ResponseFilter getResponseFilter() { - return responseFilter; - } - - public HttpLogFormatter getFormatter() { - return formatter; - } - - public HttpLogWriter getWriter() { - return writer; - } - } diff --git a/logbook-api/src/test/java/org/zalando/logbook/MockbookFactory.java b/logbook-api/src/test/java/org/zalando/logbook/MockbookFactory.java index bf67c1c7c..202b7ff31 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/MockbookFactory.java +++ b/logbook-api/src/test/java/org/zalando/logbook/MockbookFactory.java @@ -7,28 +7,24 @@ public final class MockbookFactory implements LogbookFactory { @Override public Logbook create( - @Nullable final Predicate condition, - @Nullable final RawRequestFilter rawRequestFilter, - @Nullable final RawResponseFilter rawResponseFilter, + @Nullable final Predicate condition, @Nullable final QueryFilter queryFilter, @Nullable final HeaderFilter headerFilter, @Nullable final BodyFilter bodyFilter, @Nullable final RequestFilter requestFilter, @Nullable final ResponseFilter responseFilter, - @Nullable final HttpLogFormatter formatter, - @Nullable final HttpLogWriter writer) { + @Nullable final Strategy strategy, + @Nullable final Sink sink) { return new Mockbook( condition, - rawRequestFilter, - rawResponseFilter, queryFilter, headerFilter, bodyFilter, requestFilter, responseFilter, - formatter, - writer); + strategy, + sink); } } diff --git a/logbook-api/src/test/java/org/zalando/logbook/RawHttpRequestTest.java b/logbook-api/src/test/java/org/zalando/logbook/RawHttpRequestTest.java deleted file mode 100644 index 546249db0..000000000 --- a/logbook-api/src/test/java/org/zalando/logbook/RawHttpRequestTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.mock; - -public final class RawHttpRequestTest { - - @Test - void withoutBodyShouldDefaultToNoOp() throws IOException { - final RawHttpRequest unit = mock(RawHttpRequest.class); - doCallRealMethod().when(unit).withoutBody(); - unit.withoutBody(); - } - -} diff --git a/logbook-api/src/test/java/org/zalando/logbook/RawHttpResponseTest.java b/logbook-api/src/test/java/org/zalando/logbook/RawHttpResponseTest.java deleted file mode 100644 index 0e3fec392..000000000 --- a/logbook-api/src/test/java/org/zalando/logbook/RawHttpResponseTest.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static org.mockito.Mockito.doCallRealMethod; -import static org.mockito.Mockito.mock; - -public final class RawHttpResponseTest { - - @Test - void withoutBodyShouldDefaultToNoOp() throws IOException { - final RawHttpResponse unit = mock(RawHttpResponse.class); - doCallRealMethod().when(unit).withoutBody(); - unit.withoutBody(); - } - -} diff --git a/logbook-api/src/test/java/org/zalando/logbook/RawRequestFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/RawRequestFilterTest.java deleted file mode 100644 index 7402efe94..000000000 --- a/logbook-api/src/test/java/org/zalando/logbook/RawRequestFilterTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.sameInstance; -import static org.mockito.Mockito.mock; - -public final class RawRequestFilterTest { - - @Test - void noneShouldDefaultToNoOp() { - final RawRequestFilter unit = RawRequestFilter.none(); - final RawHttpRequest request = mock(RawHttpRequest.class); - - assertThat(unit.filter(request), is(sameInstance(request))); - } - -} diff --git a/logbook-api/src/test/java/org/zalando/logbook/RawResponseFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/RawResponseFilterTest.java deleted file mode 100644 index 1aa81d60f..000000000 --- a/logbook-api/src/test/java/org/zalando/logbook/RawResponseFilterTest.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.sameInstance; -import static org.mockito.Mockito.mock; - -public final class RawResponseFilterTest { - - @Test - void noneShouldDefaultToNoOp() { - final RawResponseFilter unit = RawResponseFilter.none(); - final RawHttpResponse response = mock(RawHttpResponse.class); - - assertThat(unit.filter(response), is(sameInstance(response))); - } - -} diff --git a/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java b/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java index 4b11950bd..85a705e0a 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java @@ -17,7 +17,7 @@ public final class RequestURITest { - private final RawHttpRequest request = mock(RawHttpRequest.class); + private final HttpRequest request = mock(HttpRequest.class); @BeforeEach public void setUp() { diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java new file mode 100644 index 000000000..e4973a835 --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java @@ -0,0 +1,37 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +import java.io.IOException; + +@AllArgsConstructor +public final class BodyOnlyIfErrorStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + return request.withBody(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request, + final Sink sink) { + // do nothing + } + + @Override + public HttpResponse process(HttpRequest request, final HttpResponse response) throws IOException { + return response.withBody(); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, + final Sink sink) throws IOException { + + if (response.getStatus() >= 400) { + sink.writeBoth(correlation, request, response); + } else { + sink.writeBoth(correlation, request.withoutBody(), response.withoutBody()); + } + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpRequest.java b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpRequest.java new file mode 100644 index 000000000..6c055596a --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpRequest.java @@ -0,0 +1,42 @@ +package org.zalando.logbook; + +import static java.nio.charset.StandardCharsets.UTF_8; + +final class BodyReplacementHttpRequest implements ForwardingHttpRequest { + + private final HttpRequest request; + private final String replacement; + + public BodyReplacementHttpRequest(final HttpRequest request, final String replacement) { + this.request = request; + this.replacement = replacement; + } + + @Override + public HttpRequest delegate() { + return request; + } + + @Override + public HttpRequest withBody() { + request.withoutBody(); + return this; + } + + @Override + public HttpRequest withoutBody() { + // TODO set replacement to empty string?! + return this; + } + + @Override + public byte[] getBody() { + return replacement.getBytes(UTF_8); + } + + @Override + public String getBodyAsString() { + return replacement; + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpResponse.java b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpResponse.java new file mode 100644 index 000000000..e2b6eaabd --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementHttpResponse.java @@ -0,0 +1,42 @@ +package org.zalando.logbook; + +import static java.nio.charset.StandardCharsets.UTF_8; + +final class BodyReplacementHttpResponse implements ForwardingHttpResponse, HttpResponse { + + private final HttpResponse response; + private final String replacement; + + public BodyReplacementHttpResponse(final HttpResponse response, final String replacement) { + this.response = response; + this.replacement = replacement; + } + + @Override + public HttpResponse delegate() { + return response; + } + + @Override + public byte[] getBody() { + return replacement.getBytes(UTF_8); + } + + @Override + public String getBodyAsString() { + return replacement; + } + + @Override + public HttpResponse withBody() { + response.withoutBody(); + return this; + } + + @Override + public HttpResponse withoutBody() { + // TODO set body to empty string? + return this; + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpRequest.java b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpRequest.java deleted file mode 100644 index f39ce535c..000000000 --- a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpRequest.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.zalando.logbook; - -import java.io.IOException; - -import static java.nio.charset.StandardCharsets.UTF_8; - -final class BodyReplacementRawHttpRequest implements ForwardingRawHttpRequest { - - private final RawHttpRequest request; - private final String replacement; - - public BodyReplacementRawHttpRequest(final RawHttpRequest request, final String replacement) { - this.request = request; - this.replacement = replacement; - } - - @Override - public RawHttpRequest delegate() { - return request; - } - - @Override - public HttpRequest withBody() throws IOException { - request.withoutBody(); - return new BodyReplacementHttpRequest(request, replacement); - } - - private static final class BodyReplacementHttpRequest implements ForwardingBaseHttpRequest, HttpRequest { - - private final RawHttpRequest request; - private final String body; - - public BodyReplacementHttpRequest(final RawHttpRequest request, final String body) { - this.request = request; - this.body = body; - } - - @Override - public BaseHttpRequest delegate() { - return request; - } - - @Override - public byte[] getBody() throws IOException { - return body.getBytes(UTF_8); - } - - @Override - public String getBodyAsString() throws IOException { - return body; - } - - } - -} diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpResponse.java b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpResponse.java deleted file mode 100644 index a6c5e287d..000000000 --- a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacementRawHttpResponse.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.zalando.logbook; - -import java.io.IOException; - -import static java.nio.charset.StandardCharsets.UTF_8; - -final class BodyReplacementRawHttpResponse implements ForwardingBaseHttpResponse, RawHttpResponse { - - private final RawHttpResponse response; - private final String replacement; - - public BodyReplacementRawHttpResponse(final RawHttpResponse response, final String replacement) { - this.response = response; - this.replacement = replacement; - } - - @Override - public BaseHttpResponse delegate() { - return response; - } - - @Override - public HttpResponse withBody() throws IOException { - response.withoutBody(); - return new BodyReplacementHttpResponse(response, replacement); - } - - private static final class BodyReplacementHttpResponse implements ForwardingBaseHttpResponse, HttpResponse { - - private final RawHttpResponse response; - private final String body; - - public BodyReplacementHttpResponse(final RawHttpResponse response, final String body) { - this.response = response; - this.body = body; - } - - @Override - public BaseHttpResponse delegate() { - return response; - } - - @Override - public byte[] getBody() throws IOException { - return body.getBytes(UTF_8); - } - - @Override - public String getBodyAsString() throws IOException { - return body; - } - - } - -} diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacers.java b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacers.java index ec571ffbe..89ee2b305 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/BodyReplacers.java +++ b/logbook-core/src/main/java/org/zalando/logbook/BodyReplacers.java @@ -16,24 +16,24 @@ private BodyReplacers() { } @API(status = MAINTAINED) - public static BodyReplacer defaultValue() { + public static BodyReplacer defaultValue() { return BodyReplacer.compound(binary(), multipart(), stream()); } @API(status = MAINTAINED) - public static BodyReplacer binary() { + public static BodyReplacer binary() { final Predicate contentTypes = contentType( "application/octet-stream", "application/pdf", "audio/*", "image/*", "video/*"); return replaceBody(contentTypes, ""); } @API(status = MAINTAINED) - public static BodyReplacer multipart() { + public static BodyReplacer multipart() { return replaceBody(contentType("multipart/*"), ""); } @API(status = MAINTAINED) - public static BodyReplacer stream() { + public static BodyReplacer stream() { final Predicate contentTypes = contentType( "application/json-seq", // https://tools.ietf.org/html/rfc7464 "application/x-json-stream", // https://en.wikipedia.org/wiki/JSON_Streaming#Line_delimited_JSON @@ -43,7 +43,7 @@ public static BodyReplacer stream() { return replaceBody(contentTypes, ""); } - public static BodyReplacer replaceBody(final Predicate predicate, + public static BodyReplacer replaceBody(final Predicate predicate, final String replacement) { return message -> predicate.test(message) ? replacement : null; } diff --git a/logbook-core/src/main/java/org/zalando/logbook/ChunkingHttpLogWriter.java b/logbook-core/src/main/java/org/zalando/logbook/ChunkingHttpLogWriter.java index 4e66860f2..e62b54cf7 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/ChunkingHttpLogWriter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/ChunkingHttpLogWriter.java @@ -2,11 +2,8 @@ import lombok.SneakyThrows; import org.apiguardian.api.API; -import org.zalando.logbook.DefaultLogbook.SimpleCorrelation; -import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; import javax.annotation.Nonnull; -import java.io.IOException; import java.util.function.Consumer; import java.util.stream.Stream; @@ -32,27 +29,20 @@ public ChunkingHttpLogWriter(final int size, final HttpLogWriter writer) { } @Override - public boolean isActive(final RawHttpRequest request) throws IOException { - return writer.isActive(request); + public boolean isActive() { + return writer.isActive(); } @Override - public void writeRequest(final Precorrelation precorrelation) { - split(precorrelation.getRequest()).forEach(throwing(part -> - writer.writeRequest(new SimplePrecorrelation<>(precorrelation.getId(), part, - precorrelation.getOriginalRequest())))); + public void write(final Precorrelation precorrelation, final String request) { + split(request).forEach(throwing(part -> + writer.write(precorrelation, part))); } @Override - public void writeResponse(final Correlation correlation) { - split(correlation.getResponse()).forEach(throwing(part -> - writer.writeResponse(new SimpleCorrelation<>( - correlation.getId(), - correlation.getDuration(), - correlation.getRequest(), - part, - correlation.getOriginalRequest(), - correlation.getOriginalResponse())))); + public void write(final Correlation correlation, final String response) { + split(response).forEach(throwing(part -> + writer.write(correlation, part))); } private static Consumer throwing(final ThrowingConsumer consumer) { diff --git a/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java b/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java new file mode 100644 index 000000000..4c0742c27 --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java @@ -0,0 +1,34 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +import java.util.Collection; + +import static org.zalando.fauxpas.FauxPas.throwingConsumer; + +@AllArgsConstructor +public final class CompositeSink implements Sink { + + private final Collection sinks; + + @Override + public boolean isActive() { + return sinks.stream().anyMatch(Sink::isActive); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request) { + sinks.forEach(throwingConsumer(sink -> sink.write(precorrelation, request))); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response) { + sinks.forEach(throwingConsumer(sink -> sink.write(correlation, request, response))); + } + + @Override + public void writeBoth(final Correlation correlation, final HttpRequest request, final HttpResponse response) { + sinks.forEach(throwingConsumer(sink -> sink.writeBoth(correlation, request, response))); + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/Conditions.java b/logbook-core/src/main/java/org/zalando/logbook/Conditions.java index ccdda475a..ddd4cfa51 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/Conditions.java +++ b/logbook-core/src/main/java/org/zalando/logbook/Conditions.java @@ -23,52 +23,52 @@ private Conditions() { } @SafeVarargs - public static Predicate exclude(final Predicate... predicates) { + public static Predicate exclude(final Predicate... predicates) { return exclude(Arrays.asList(predicates)); } - public static Predicate exclude(final Collection> predicates) { + public static Predicate exclude(final Collection> predicates) { return predicates.stream() .map(Predicate::negate) .reduce(Predicate::and) .orElse($ -> true); } - public static Predicate requestTo(final String pattern) { + public static Predicate requestTo(final String pattern) { final Predicate predicate = Glob.compile(pattern); return pattern.startsWith("/") ? - requestTo(BaseHttpRequest::getPath, predicate) : + requestTo(HttpRequest::getPath, predicate) : requestTo(request -> reconstruct(request, SCHEME, AUTHORITY, PATH), predicate); } - private static Predicate requestTo(final Function extractor, + private static Predicate requestTo(final Function extractor, final Predicate predicate) { return request -> predicate.test(extractor.apply(request)); } - public static Predicate contentType(final String... contentTypes) { + public static Predicate contentType(final String... contentTypes) { final Predicate query = MediaTypeQuery.compile(contentTypes); return message -> query.test(message.getContentType()); } - public static Predicate withoutContentType() { + public static Predicate withoutContentType() { return message -> message.getContentType() == null; } - public static Predicate header(final String key, final String value) { + public static Predicate header(final String key, final String value) { return message -> message.getHeaders().getOrDefault(key, emptyList()).contains(value); } - public static Predicate header(final String key, final Predicate predicate) { + public static Predicate header(final String key, final Predicate predicate) { return message -> message.getHeaders().getOrDefault(key, emptyList()).stream().anyMatch(predicate); } - public static Predicate header(final BiPredicate predicate) { + public static Predicate header(final BiPredicate predicate) { return message -> message.getHeaders().entrySet().stream() .anyMatch(e -> diff --git a/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java b/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java index 3995e707c..86d8b1729 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java @@ -26,8 +26,7 @@ public CurlHttpLogFormatter(final HttpLogFormatter fallback) { } @Override - public String format(final Precorrelation precorrelation) throws IOException { - final HttpRequest request = precorrelation.getRequest(); + public String format(final Precorrelation precorrelation, final HttpRequest request) throws IOException { final List command = new ArrayList<>(); command.add(precorrelation.getId()); @@ -65,8 +64,9 @@ private static String escape(final String s) { } @Override - public String format(final Correlation correlation) throws IOException { - return fallback.format(correlation); + public String format(final Correlation correlation, final HttpResponse response) + throws IOException { + return fallback.format(correlation, response); } } diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java index 8a945e37d..ce087b2b7 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java @@ -97,8 +97,8 @@ public final class DefaultHttpLogFormatter implements HttpLogFormatter { } @Override - public String format(final Precorrelation precorrelation) throws IOException { - return format(prepare(precorrelation)); + public String format(final Precorrelation precorrelation, final HttpRequest request) throws IOException { + return format(prepare(precorrelation, request)); } /** @@ -107,21 +107,20 @@ public String format(final Precorrelation precorrelation) throws IO * @param precorrelation the request correlation * @return a line-separated HTTP request * @throws IOException if reading body fails - * @see #prepare(Correlation) + * @see #prepare(Correlation, HttpResponse) * @see #format(List) - * @see JsonHttpLogFormatter#prepare(Precorrelation) + * @see JsonHttpLogFormatter#prepare(Precorrelation, HttpRequest) */ @API(status = EXPERIMENTAL) - public List prepare(final Precorrelation precorrelation) throws IOException { - final HttpRequest request = precorrelation.getRequest(); + public List prepare(final Precorrelation precorrelation, final HttpRequest request) throws IOException { final String requestLine = String.format("%s %s %s", request.getMethod(), request.getRequestUri(), request.getProtocolVersion()); return prepare(request, "Request", precorrelation.getId(), requestLine); } @Override - public String format(final Correlation correlation) throws IOException { - return format(prepare(correlation)); + public String format(final Correlation correlation, final HttpResponse response) throws IOException { + return format(prepare(correlation, response)); } /** @@ -132,13 +131,12 @@ public String format(final Correlation correlation) t * @param correlation the correlated request and response pair * @return a line-separated HTTP response * @throws IOException if reading body fails - * @see #prepare(Precorrelation) + * @see #prepare(Precorrelation, HttpRequest) * @see #format(List) - * @see JsonHttpLogFormatter#prepare(Correlation) + * @see JsonHttpLogFormatter#prepare(Correlation, HttpResponse) */ @API(status = EXPERIMENTAL) - public List prepare(final Correlation correlation) throws IOException { - final HttpResponse response = correlation.getResponse(); + public List prepare(final Correlation correlation, final HttpResponse response) throws IOException { final int status = response.getStatus(); final String reasonPhrase = REASON_PHRASES.getOrDefault(Integer.toString(status), ""); final String statusLine = String.format("%s %d %s", response.getProtocolVersion(), status, reasonPhrase).trim(); @@ -189,8 +187,8 @@ private String formatHeader(final Map.Entry entry) { * * @param lines lines of an HTTP message * @return the whole message as a single string, separated by new lines - * @see #prepare(Precorrelation) - * @see #prepare(Correlation) + * @see #prepare(Precorrelation, HttpRequest) + * @see #prepare(Correlation, HttpResponse) * @see JsonHttpLogFormatter#format(Map) */ @API(status = EXPERIMENTAL) diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java index 124ead1cb..b33f7c06c 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogWriter.java @@ -67,18 +67,18 @@ Logger getLogger() { } @Override - public boolean isActive(final RawHttpRequest request) { + public boolean isActive() { return activator.test(logger); } @Override - public void writeRequest(final Precorrelation precorrelation) { - consumer.accept(logger, precorrelation.getRequest()); + public void write(final Precorrelation precorrelation, final String request) { + consumer.accept(logger, request); } @Override - public void writeResponse(final Correlation correlation) { - consumer.accept(logger, correlation.getResponse()); + public void write(final Correlation correlation, final String response) { + consumer.accept(logger, response); } } diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java index 35f24ec57..5f5f26dd2 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java @@ -1,122 +1,90 @@ package org.zalando.logbook; +import lombok.AllArgsConstructor; + import java.io.IOException; import java.time.Clock; import java.time.Duration; import java.time.Instant; -import java.util.Optional; import java.util.concurrent.ThreadLocalRandom; import java.util.function.Predicate; +@AllArgsConstructor final class DefaultLogbook implements Logbook { - private final Predicate predicate; - private final RawRequestFilter rawRequestFilter; - private final RawResponseFilter rawResponseFilter; + private final Predicate predicate; private final RequestFilter requestFilter; private final ResponseFilter responseFilter; - private final HttpLogFormatter formatter; - private final HttpLogWriter writer; + private final Strategy strategy; + private final Sink sink; private final Clock clock = Clock.systemUTC(); - DefaultLogbook( - final Predicate predicate, - final RawRequestFilter rawRequestFilter, - final RawResponseFilter rawResponseFilter, - final RequestFilter requestFilter, - final ResponseFilter responseFilter, - final HttpLogFormatter formatter, - final HttpLogWriter writer) { - this.predicate = predicate; - this.rawRequestFilter = rawRequestFilter; - this.rawResponseFilter = rawResponseFilter; - this.requestFilter = requestFilter; - this.responseFilter = responseFilter; - this.formatter = formatter; - this.writer = writer; - } - @Override - public Optional write(final RawHttpRequest rawHttpRequest) throws IOException { - final Instant start = Instant.now(clock); - if (writer.isActive(rawHttpRequest) && predicate.test(rawHttpRequest)) { - final String correlationId = generateCorrelationId(); - final RawHttpRequest filteredRawHttpRequest = rawRequestFilter.filter(rawHttpRequest); - final HttpRequest request = requestFilter.filter(filteredRawHttpRequest.withBody()); - - final Precorrelation precorrelation = - new SimplePrecorrelation<>(correlationId, request, request); - final String formattedRequest = formatter.format(precorrelation); - writer.writeRequest(new SimplePrecorrelation<>(correlationId, formattedRequest, request)); - - return Optional.of(rawHttpResponse -> { - final Instant end = Instant.now(clock); - final Duration duration = Duration.between(start, end); - final RawHttpResponse filteredRawHttpResponse = rawResponseFilter.filter(rawHttpResponse); - final HttpResponse response = responseFilter.filter(filteredRawHttpResponse.withBody()); - final Correlation correlation = - new SimpleCorrelation<>(correlationId, duration, request, response, request, response); - final String formattedResponse = formatter.format(correlation); - writer.writeResponse(new SimpleCorrelation<>(correlationId, duration, - formattedRequest, formattedResponse, request, response)); - }); + public RequestWritingStage process(final HttpRequest originalRequest) throws IOException { + if (sink.isActive() && predicate.test(originalRequest)) { + final Precorrelation precorrelation = new SimplePrecorrelation(clock); + final HttpRequest processedRequest = strategy.process(originalRequest); + + return () -> { + final HttpRequest filteredRequest = requestFilter.filter(processedRequest); + strategy.write(precorrelation, filteredRequest, sink); + return originalResponse -> { + final HttpResponse processedResponse = strategy.process(originalRequest, originalResponse); + return () -> { + final HttpResponse filteredResponse = responseFilter.filter(processedResponse); + strategy.write(precorrelation.correlate(), filteredRequest, filteredResponse, sink); + }; + }; + }; } else { - return Optional.empty(); + return () -> response -> () -> { + // nothing to do + }; } } - private static String generateCorrelationId() { - // set most significant bit to produce fixed length string - return Long.toHexString(ThreadLocalRandom.current().nextLong() | Long.MIN_VALUE); - } - - static class SimplePrecorrelation implements Precorrelation { + static class SimplePrecorrelation implements Precorrelation { private final String id; - private final I request; - private final HttpRequest originalRequest; + private final Clock clock; + private final Instant start; + + SimplePrecorrelation(final Clock clock) { + this(generateCorrelationId(), clock); + } - public SimplePrecorrelation(final String id, final I request, final HttpRequest originalRequest) { + // visible for testing + SimplePrecorrelation(final String id, final Clock clock) { this.id = id; - this.request = request; - this.originalRequest = originalRequest; + this.clock = clock; + this.start = Instant.now(clock); } - @Override - public String getId() { - return id; + // TODO interface? + private static String generateCorrelationId() { + // set most significant bit to produce fixed length string + return Long.toHexString(ThreadLocalRandom.current().nextLong() | Long.MIN_VALUE); } @Override - public I getRequest() { - return request; + public String getId() { + return id; } @Override - public HttpRequest getOriginalRequest() { - return originalRequest; + public Correlation correlate() { + final Instant end = Instant.now(clock); + final Duration duration = Duration.between(start, end); + return new SimpleCorrelation(id, duration); } } - static class SimpleCorrelation implements Correlation { + @AllArgsConstructor + static class SimpleCorrelation implements Correlation { private final String id; private final Duration duration; - private final I request; - private final O response; - private final HttpRequest originalRequest; - private final HttpResponse originalResponse; - - SimpleCorrelation(final String id, final Duration duration, final I request, final O response, - final HttpRequest originalRequest, final HttpResponse originalResponse) { - this.id = id; - this.duration = duration; - this.request = request; - this.response = response; - this.originalRequest = originalRequest; - this.originalResponse = originalResponse; - } @Override public String getId() { @@ -128,26 +96,6 @@ public Duration getDuration() { return duration; } - @Override - public I getRequest() { - return request; - } - - @Override - public O getResponse() { - return response; - } - - @Override - public HttpRequest getOriginalRequest() { - return originalRequest; - } - - @Override - public HttpResponse getOriginalResponse() { - return originalResponse; - } - } } diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbookFactory.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbookFactory.java index 6534e2409..05cad8ab2 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbookFactory.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbookFactory.java @@ -14,19 +14,16 @@ public final class DefaultLogbookFactory implements LogbookFactory { @Override public Logbook create( - @Nullable final Predicate nullableCondition, - @Nullable final RawRequestFilter nullableRawRequestFilter, - @Nullable final RawResponseFilter nullableRawResponseFilter, + @Nullable final Predicate nullableCondition, @Nullable final QueryFilter queryFilter, @Nullable final HeaderFilter headerFilter, @Nullable final BodyFilter bodyFilter, @Nullable final RequestFilter requestFilter, @Nullable final ResponseFilter responseFilter, - @Nullable final HttpLogFormatter formatter, - @Nullable final HttpLogWriter writer) { + @Nullable final Strategy strategy, + @Nullable final Sink sink) { - - final Predicate condition = Optional.ofNullable(nullableCondition) + final Predicate condition = Optional.ofNullable(nullableCondition) .orElse($ -> true); final HeaderFilter header = Optional.ofNullable(headerFilter) @@ -35,20 +32,16 @@ public Logbook create( final BodyFilter body = Optional.ofNullable(bodyFilter) .orElseGet(BodyFilters::defaultValue); - final RawRequestFilter rawRequestFilter = Optional.ofNullable(nullableRawRequestFilter) - .orElseGet(RawRequestFilters::defaultValue); - - final RawResponseFilter rawResponseFilter = Optional.ofNullable(nullableRawResponseFilter) - .orElseGet(RawResponseFilters::defaultValue); - return new DefaultLogbook( condition, - rawRequestFilter, - rawResponseFilter, combine(queryFilter, header, body, requestFilter), combine(header, body, responseFilter), - Optional.ofNullable(formatter).orElseGet(DefaultHttpLogFormatter::new), - Optional.ofNullable(writer).orElseGet(DefaultHttpLogWriter::new) + Optional.ofNullable(strategy).orElseGet(DefaultStrategy::new), + Optional.ofNullable(sink).orElseGet(() -> + new DefaultSink( + new DefaultHttpLogFormatter(), + new DefaultHttpLogWriter() + )) ); } @@ -62,7 +55,7 @@ private RequestFilter combine( final QueryFilter query = Optional.ofNullable(queryFilter).orElseGet(QueryFilters::defaultValue); return RequestFilter.merge( - Optional.ofNullable(requestFilter).orElseGet(RequestFilter::none), + Optional.ofNullable(requestFilter).orElseGet(RequestFilters::defaultValue), request -> new FilteredHttpRequest(request, query, headerFilter, bodyFilter)); } @@ -73,7 +66,7 @@ private ResponseFilter combine( @Nullable final ResponseFilter responseFilter) { return ResponseFilter.merge( - Optional.ofNullable(responseFilter).orElseGet(ResponseFilter::none), + Optional.ofNullable(responseFilter).orElseGet(ResponseFilters::defaultValue), response -> new FilteredHttpResponse(response, headerFilter, bodyFilter)); } } diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultSink.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultSink.java new file mode 100644 index 000000000..a0261a60e --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultSink.java @@ -0,0 +1,35 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +import java.io.IOException; + +@AllArgsConstructor +public final class DefaultSink implements Sink { + + private final HttpLogFormatter formatter; + private final HttpLogWriter writer; + + @Override + public boolean isActive() { + return writer.isActive(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request) throws IOException { + writer.write(precorrelation, formatter.format(precorrelation, request)); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response) + throws IOException { + writer.write(correlation, formatter.format(correlation, response)); + } + + @Override + public void writeBoth(final Correlation correlation, final HttpRequest request, final HttpResponse response) throws IOException { + write(correlation, request); + write(correlation, request, response); + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultStrategy.java new file mode 100644 index 000000000..b1396439f --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultStrategy.java @@ -0,0 +1,8 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public final class DefaultStrategy implements Strategy { + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java new file mode 100644 index 000000000..db6e059fd --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java @@ -0,0 +1,35 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +import java.io.IOException; + +@AllArgsConstructor +public final class ErrorResponseOnlyStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + return request.withBody(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request, + final Sink sink) { + // do nothing + } + + @Override + public HttpResponse process(HttpRequest request, final HttpResponse response) throws IOException { + return response.withBody(); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, + final Sink sink) throws IOException { + + if (response.getStatus() >= 400) { + sink.writeBoth(correlation, request, response); + } + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java b/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java index de061d637..3deb0b981 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java +++ b/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java @@ -36,7 +36,7 @@ public static HeaderFilter replaceHeaders(final BiPredicate pred public static HeaderFilter eachHeader(final BinaryOperator operator) { return headers -> { - final BaseHttpMessage.HeadersBuilder result = new BaseHttpMessage.HeadersBuilder(); + final HttpMessage.HeadersBuilder result = new HttpMessage.HeadersBuilder(); headers.forEach((key, values) -> values.stream() @@ -53,7 +53,7 @@ public static HeaderFilter removeHeaders(final Predicate keyPredicate) { public static HeaderFilter removeHeaders(final BiPredicate predicate) { return headers -> { - final BaseHttpMessage.HeadersBuilder result = new BaseHttpMessage.HeadersBuilder(); + final HttpMessage.HeadersBuilder result = new HttpMessage.HeadersBuilder(); headers.forEach((key, values) -> values.stream() diff --git a/logbook-core/src/main/java/org/zalando/logbook/PreparedHttpLogFormatter.java b/logbook-core/src/main/java/org/zalando/logbook/PreparedHttpLogFormatter.java index 11addcd80..5f7430806 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/PreparedHttpLogFormatter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/PreparedHttpLogFormatter.java @@ -14,13 +14,14 @@ public interface PreparedHttpLogFormatter extends HttpLogFormatter { @Override - default String format(final Precorrelation precorrelation) throws IOException { - return format(prepare(precorrelation)); + default String format(final Precorrelation precorrelation, final HttpRequest request) throws IOException { + return format(prepare(precorrelation, request)); } @Override - default String format(final Correlation correlation) throws IOException { - return format(prepare(correlation)); + default String format(final Correlation correlation, final HttpResponse response) + throws IOException { + return format(prepare(correlation, response)); } /** @@ -29,8 +30,8 @@ default String format(final Correlation correlation) * @param content individual parts of an HTTP message * @return the whole message as a JSON object * @throws IOException if writing JSON output fails - * @see #prepare(Precorrelation) - * @see #prepare(Correlation) + * @see #prepare(Precorrelation, HttpRequest) + * @see #prepare(Correlation, HttpResponse) * @see DefaultHttpLogFormatter#format(List) */ String format(Map content) throws IOException; @@ -41,13 +42,13 @@ default String format(final Correlation correlation) * @param precorrelation the request correlation * @return a map containing HTTP request attributes * @throws IOException if reading body fails - * @see #prepare(Correlation) + * @see #prepare(Correlation, HttpResponse) * @see #format(Map) - * @see DefaultHttpLogFormatter#prepare(Precorrelation) + * @see DefaultHttpLogFormatter#prepare(Precorrelation, HttpRequest) */ - default Map prepare(final Precorrelation precorrelation) throws IOException { + default Map prepare(final Precorrelation precorrelation, final HttpRequest request) + throws IOException { final String correlationId = precorrelation.getId(); - final HttpRequest request = precorrelation.getRequest(); final Map content = new LinkedHashMap<>(); @@ -71,13 +72,11 @@ default Map prepare(final Precorrelation precorrela * @param correlation the response correlation * @return a map containing HTTP response attributes * @throws IOException if reading body fails - * @see #prepare(Correlation) + * @see #prepare(Correlation, HttpResponse) * @see #format(Map) - * @see DefaultHttpLogFormatter#prepare(Correlation) + * @see DefaultHttpLogFormatter#prepare(Correlation, HttpResponse) */ - default Map prepare(final Correlation correlation) throws IOException { - final HttpResponse response = correlation.getResponse(); - + default Map prepare(final Correlation correlation, final HttpResponse response) throws IOException { final Map content = new LinkedHashMap<>(); content.put("origin", Origins.translate(response.getOrigin())); @@ -102,8 +101,7 @@ static void addUnless( final Map content, final String key, final T element, - final Predicate predicate - ) { + final Predicate predicate) { if (!predicate.test(element)) { content.put(key, element); } diff --git a/logbook-core/src/main/java/org/zalando/logbook/RawRequestFilters.java b/logbook-core/src/main/java/org/zalando/logbook/RequestFilters.java similarity index 64% rename from logbook-core/src/main/java/org/zalando/logbook/RawRequestFilters.java rename to logbook-core/src/main/java/org/zalando/logbook/RequestFilters.java index 31f0d6f84..f4b4bb047 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/RawRequestFilters.java +++ b/logbook-core/src/main/java/org/zalando/logbook/RequestFilters.java @@ -8,23 +8,23 @@ import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public final class RawRequestFilters { +public final class RequestFilters { - private RawRequestFilters() { + private RequestFilters() { } @API(status = MAINTAINED) - public static RawRequestFilter defaultValue() { + public static RequestFilter defaultValue() { return replaceBody(BodyReplacers.defaultValue()); } - public static RawRequestFilter replaceBody(final BodyReplacer replacer) { + public static RequestFilter replaceBody(final BodyReplacer replacer) { return request -> { @Nullable final String replacement = replacer.replace(request); return replacement == null ? request : - new BodyReplacementRawHttpRequest(request, replacement); + new BodyReplacementHttpRequest(request, replacement); }; } diff --git a/logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java new file mode 100644 index 000000000..4f12825d3 --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java @@ -0,0 +1,15 @@ +package org.zalando.logbook; + +public final class RequestOnlyStrategy implements Strategy { + + @Override + public HttpResponse process(HttpRequest request, final HttpResponse response) { + return response.withoutBody(); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, final Sink sink) { + // do nothing + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/RawResponseFilters.java b/logbook-core/src/main/java/org/zalando/logbook/ResponseFilters.java similarity index 63% rename from logbook-core/src/main/java/org/zalando/logbook/RawResponseFilters.java rename to logbook-core/src/main/java/org/zalando/logbook/ResponseFilters.java index 143745d9a..e51f99f16 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/RawResponseFilters.java +++ b/logbook-core/src/main/java/org/zalando/logbook/ResponseFilters.java @@ -8,23 +8,23 @@ import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public final class RawResponseFilters { +public final class ResponseFilters { - private RawResponseFilters() { + private ResponseFilters() { } @API(status = MAINTAINED) - public static RawResponseFilter defaultValue() { + public static ResponseFilter defaultValue() { return replaceBody(BodyReplacers.defaultValue()); } - public static RawResponseFilter replaceBody(final BodyReplacer replacer) { + public static ResponseFilter replaceBody(final BodyReplacer replacer) { return response -> { @Nullable final String replacement = replacer.replace(response); return replacement == null ? response : - new BodyReplacementRawHttpResponse(response, replacement); + new BodyReplacementHttpResponse(response, replacement); }; } diff --git a/logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java new file mode 100644 index 000000000..3b84ddd4a --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java @@ -0,0 +1,18 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +@AllArgsConstructor +public final class ResponseOnlyStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) { + return request.withoutBody(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request, final Sink sink) { + // do nothing + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java new file mode 100644 index 000000000..dd961bfe4 --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java @@ -0,0 +1,15 @@ +package org.zalando.logbook; + +import lombok.AllArgsConstructor; + +import java.io.IOException; + +@AllArgsConstructor +public final class SomeRequestsWithoutBodyStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + return request.getRequestUri().contains("/attachments") ? request.withoutBody() : request.withBody(); + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/StreamHttpLogWriter.java b/logbook-core/src/main/java/org/zalando/logbook/StreamHttpLogWriter.java index 9e6380fad..a8dc65a46 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/StreamHttpLogWriter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/StreamHttpLogWriter.java @@ -21,13 +21,13 @@ public StreamHttpLogWriter(final PrintStream stream) { } @Override - public void writeRequest(final Precorrelation precorrelation) throws IOException { - stream.println(precorrelation.getRequest()); + public void write(final Precorrelation precorrelation, final String request) throws IOException { + stream.println(request); } @Override - public void writeResponse(final Correlation correlation) throws IOException { - stream.println(correlation.getResponse()); + public void write(final Correlation correlation, final String response) throws IOException { + stream.println(response); } } diff --git a/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java index b2a91b57b..ba57b1ee5 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java @@ -2,18 +2,19 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.zalando.logbook.DefaultLogbook.SimpleCorrelation; import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; import java.io.IOException; +import java.time.Clock; import java.util.List; import static java.time.Duration.ZERO; -import static java.util.stream.Collectors.toList; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.mockito.ArgumentCaptor.forClass; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -23,16 +24,9 @@ public final class ChunkingHttpLogWriterTest { private final HttpLogWriter delegate = mock(HttpLogWriter.class); private final HttpLogWriter unit = new ChunkingHttpLogWriter(20, delegate); - @SuppressWarnings("unchecked") - private final ArgumentCaptor> requestCaptor = forClass(Precorrelation.class); - - @SuppressWarnings("unchecked") - private final ArgumentCaptor> responseCaptor = forClass(Correlation.class); - @Test - void shouldDelegateActive() throws IOException { - final RawHttpRequest request = mock(RawHttpRequest.class); - assertThat(unit.isActive(request), is(false)); + void shouldDelegateActive() { + assertThat(unit.isActive(), is(false)); } @Test @@ -49,13 +43,11 @@ void shouldWriteRequestInChunksIfLengthExceeded() throws IOException { } private List captureRequest(final String request) throws IOException { - unit.writeRequest(new SimplePrecorrelation<>("id", request, MockHttpRequest.create())); + unit.write(new SimplePrecorrelation(Clock.systemUTC()), request); - verify(delegate, atLeastOnce()).writeRequest(requestCaptor.capture()); - - return requestCaptor.getAllValues().stream() - .map(Precorrelation::getRequest) - .collect(toList()); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(delegate, atLeastOnce()).write(any(Precorrelation.class), captor.capture()); + return captor.getAllValues(); } @Test @@ -83,13 +75,10 @@ void shouldCreateWithSizeOfOne() { } private List captureResponse(final String response) throws IOException { - unit.writeResponse(new DefaultLogbook.SimpleCorrelation<>("id", ZERO, "", response, - MockHttpRequest.create(), MockHttpResponse.create())); - - verify(delegate, atLeastOnce()).writeResponse(responseCaptor.capture()); + unit.write(new SimpleCorrelation("id", ZERO), response); - return responseCaptor.getAllValues().stream() - .map(Correlation::getResponse) - .collect(toList()); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(delegate, atLeastOnce()).write(any(), captor.capture()); + return captor.getAllValues(); } } diff --git a/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java b/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java index 7d539ceab..972523e34 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java @@ -15,57 +15,57 @@ public final class ConditionsTest { - private final MockRawHttpRequest request = MockRawHttpRequest.create() + private final MockHttpRequest request = MockHttpRequest.create() .withHeaders(MockHeaders.of("X-Secret", "true")) .withContentType("text/plain"); @Test void excludeShouldMatchIfNoneMatches() { - final Predicate unit = exclude(requestTo("/admin"), contentType("application/json")); + final Predicate unit = exclude(requestTo("/admin"), contentType("application/json")); assertThat(unit.test(request), is(true)); } @Test void excludeNotShouldMatchIfAnyMatches() { - final Predicate unit = exclude(requestTo("/admin"), contentType("text/plain")); + final Predicate unit = exclude(requestTo("/admin"), contentType("text/plain")); assertThat(unit.test(request), is(false)); } @Test void excludeNotShouldMatchIfAllMatches() { - final Predicate unit = exclude(requestTo("/"), contentType("text/plain")); + final Predicate unit = exclude(requestTo("/"), contentType("text/plain")); assertThat(unit.test(request), is(false)); } @Test void excludeShouldDefaultToAlwaysTrue() { - final Predicate unit = exclude(); + final Predicate unit = exclude(); assertThat(unit.test(null), is(true)); } @Test void requestToShouldMatchURI() { - final Predicate unit = requestTo("http://localhost/"); + final Predicate unit = requestTo("http://localhost/"); assertThat(unit.test(request), is(true)); } @Test void requestToShouldNotMatchURIPattern() { - final Predicate unit = requestTo("http://192.168.0.1/*"); + final Predicate unit = requestTo("http://192.168.0.1/*"); assertThat(unit.test(request), is(false)); } @Test void requestToShouldIgnoreQueryParameters() { - final Predicate unit = requestTo("http://localhost/*"); + final Predicate unit = requestTo("http://localhost/*"); - final MockRawHttpRequest request = MockRawHttpRequest.create() + final MockHttpRequest request = MockHttpRequest.create() .withQuery("location=/bar"); assertThat(unit.test(request), is(true)); @@ -73,70 +73,70 @@ void requestToShouldIgnoreQueryParameters() { @Test void requestToShouldMatchPath() { - final Predicate unit = requestTo("/"); + final Predicate unit = requestTo("/"); assertThat(unit.test(request), is(true)); } @Test void contentTypeShouldMatch() { - final Predicate unit = contentType("text/plain"); + final Predicate unit = contentType("text/plain"); assertThat(unit.test(request), is(true)); } @Test void contentTypeShouldNotMatch() { - final Predicate unit = contentType("application/json"); + final Predicate unit = contentType("application/json"); assertThat(unit.test(request), is(false)); } @Test void withoutContentTypeShouldMatch() { - final Predicate unit = withoutContentType(); + final Predicate unit = withoutContentType(); assertThat(unit.test(request.withContentType(null)), is(true)); } @Test void withoutContentTypeShouldNotMatch() { - final Predicate unit = withoutContentType(); + final Predicate unit = withoutContentType(); assertThat(unit.test(request), is(false)); } @Test void headerShouldMatchNameAndValue() { - final Predicate unit = header("X-Secret", "true"); + final Predicate unit = header("X-Secret", "true"); assertThat(unit.test(request), is(true)); } @Test void headerShouldNotMatchNameAndValue() { - final Predicate unit = header("X-Secret", "false"); + final Predicate unit = header("X-Secret", "false"); assertThat(unit.test(request), is(false)); } @Test void headerShouldMatchNameAndValuePredicate() { - final Predicate unit = header("X-Secret", asList("true", "1")::contains); + final Predicate unit = header("X-Secret", asList("true", "1")::contains); assertThat(unit.test(request), is(true)); } @Test void headerShouldNotMatchNameAndValuePredicate() { - final Predicate unit = header("X-Secret", asList("yes", "1")::contains); + final Predicate unit = header("X-Secret", asList("yes", "1")::contains); assertThat(unit.test(request), is(false)); } @Test void headerShouldMatchPredicate() { - final Predicate unit = header((name, value) -> + final Predicate unit = header((name, value) -> name.equalsIgnoreCase("X-Secret") && value.equalsIgnoreCase("true")); assertThat(unit.test(request), is(true)); @@ -144,7 +144,7 @@ void headerShouldMatchPredicate() { @Test void headerShouldNotMatchPredicate() { - final Predicate unit = header((name, value) -> + final Predicate unit = header((name, value) -> name.equalsIgnoreCase("X-Secret") && value.equalsIgnoreCase("false")); assertThat(unit.test(request), is(false)); @@ -152,7 +152,7 @@ void headerShouldNotMatchPredicate() { @Test void headerShouldNotMatchPredicateWhenHeaderIsAbsent() { - final Predicate unit = header("X-Absent", v -> true); + final Predicate unit = header("X-Absent", v -> true); assertThat(unit.test(request), is(false)); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java index 6b91073ed..854cac0cd 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java @@ -6,6 +6,7 @@ import java.io.IOException; +import static java.time.Clock.systemUTC; import static java.time.Duration.ZERO; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -28,7 +29,7 @@ void shouldLogRequest() throws IOException { .withBodyAsString("Hello, world!"); final HttpLogFormatter unit = new CurlHttpLogFormatter(); - final String curl = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String curl = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); assertThat(curl, is("c9408eaa-677d-11e5-9457-10ddb1ee7671 " + "curl -v -X GET 'http://localhost/test?limit=1' -H 'Accept: application/json' -H 'Content-Type: text/plain' --data-binary 'Hello, world!'")); @@ -42,7 +43,7 @@ void shouldLogRequestWithoutBody() throws IOException { .withHeaders(MockHeaders.of("Accept", "application/json")); final HttpLogFormatter unit = new CurlHttpLogFormatter(); - final String curl = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String curl = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); assertThat(curl, is("0eae9f6c-6824-11e5-8b0a-10ddb1ee7671 " + "curl -v -X GET 'http://localhost/test' -H 'Accept: application/json'")); @@ -62,7 +63,7 @@ void shouldEscape() throws IOException { .withBodyAsString("{\"message\":\"Hello, 'world'!\"}"); final HttpLogFormatter unit = new CurlHttpLogFormatter(); - final String curl = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String curl = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); assertThat(curl, is("c9408eaa-677d-11e5-9457-10ddb1ee7671 " + "curl -v -X GET 'http://localhost/test?char=\\'' -H 'Foo\\'Bar: Baz' --data-binary '{\"message\":\"Hello, \\'world\\'!\"}'")); @@ -76,12 +77,10 @@ void shouldDelegateLogResponse() throws IOException { final MockHttpRequest request = MockHttpRequest.create(); final MockHttpResponse response = MockHttpResponse.create(); - final Correlation correlation = new SimpleCorrelation<>( - "3881ae92-6824-11e5-921b-10ddb1ee7671", ZERO, request, response, request, response); + final Correlation correlation = new SimpleCorrelation("3881ae92-6824-11e5-921b-10ddb1ee7671", ZERO); - unit.format(correlation); - - verify(fallback).format(correlation); + unit.format(correlation, response); + verify(fallback).format(correlation, response); } } diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java index 5d7097f03..69cf35b85 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java @@ -6,6 +6,7 @@ import java.io.IOException; +import static java.time.Clock.systemUTC; import static java.time.Duration.ofMillis; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -27,7 +28,7 @@ void shouldLogRequest() throws IOException { "Content-Type", "text/plain")) .withBodyAsString("Hello, world!"); - final String http = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String http = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); assertThat(http, is("Incoming Request: c9408eaa-677d-11e5-9457-10ddb1ee7671\n" + "GET http://localhost/test?limit=1 HTTP/1.0\n" + @@ -48,7 +49,7 @@ void shouldLogRequestWithoutQueryParameters() throws IOException { "Content-Type", "text/plain")) .withBodyAsString("Hello, world!"); - final String http = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String http = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); assertThat(http, is("Outgoing Request: 2bd05240-6827-11e5-bbee-10ddb1ee7671\n" + "GET http://localhost/test HTTP/1.1\n" + @@ -65,7 +66,7 @@ void shouldLogRequestWithoutBody() throws IOException { .withPath("/test") .withHeaders(MockHeaders.of("Accept", "application/json")); - final String http = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String http = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); assertThat(http, is("Incoming Request: 0eae9f6c-6824-11e5-8b0a-10ddb1ee7671\n" + "GET http://localhost/test HTTP/1.1\n" + @@ -83,8 +84,7 @@ void shouldLogResponse() throws IOException { .withHeaders(MockHeaders.of("Content-Type", "application/json")) .withBodyAsString("{\"success\":true}"); - final String http = unit.format(new SimpleCorrelation<>(correlationId, ofMillis(125), request, response, - request, response)); + final String http = unit.format(new SimpleCorrelation(correlationId, ofMillis(125)), response); assertThat(http, is("Incoming Response: 2d51bc02-677e-11e5-8b9b-10ddb1ee7671\n" + "Duration: 125 ms\n" + @@ -103,8 +103,7 @@ void shouldLogResponseWithoutBody() throws IOException { .withStatus(400) .withHeaders(MockHeaders.of("Content-Type", "application/json")); - final String http = unit.format(new SimpleCorrelation<>(correlationId, ofMillis(100), request, response, - request, response)); + final String http = unit.format(new SimpleCorrelation(correlationId, ofMillis(100)), response); assertThat(http, is("Outgoing Response: 3881ae92-6824-11e5-921b-10ddb1ee7671\n" + "Duration: 100 ms\n" + diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java index abf05c31e..c4a80e011 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java @@ -9,6 +9,7 @@ import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; import java.io.IOException; +import java.time.Clock; import java.util.Arrays; import java.util.function.BiConsumer; import java.util.function.Predicate; @@ -45,9 +46,8 @@ private static BiConsumer consumer(final BiConsumer isEnabled) - throws IOException { - unit.isActive(mock(RawHttpRequest.class)); + void shouldBeEnabled(final HttpLogWriter unit, final Logger logger, final Predicate isEnabled) { + unit.isActive(); isEnabled.test(verify(logger)); } @@ -57,7 +57,7 @@ void shouldBeEnabled(final HttpLogWriter unit, final Logger logger, final Predic void shouldLogRequestWithCorrectLevel(final HttpLogWriter unit, final Logger logger, @SuppressWarnings("unused") final Predicate isEnabled, final BiConsumer log) throws IOException { - unit.writeRequest(new SimplePrecorrelation<>("1", "foo", MockHttpRequest.create())); + unit.write(new SimplePrecorrelation(Clock.systemUTC()), "foo"); log.accept(verify(logger), "foo"); } @@ -67,8 +67,7 @@ void shouldLogRequestWithCorrectLevel(final HttpLogWriter unit, final Logger log void shouldLogResponseWithCorrectLevel(final HttpLogWriter unit, final Logger logger, @SuppressWarnings("unused") final Predicate isEnabled, final BiConsumer log) throws IOException { - unit.writeResponse(new SimpleCorrelation<>("1", ZERO, "foo", "bar", - MockHttpRequest.create(), MockHttpResponse.create())); + unit.write(new SimpleCorrelation("1", ZERO), "bar"); log.accept(verify(logger), "bar"); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java index cde9540ef..08f883075 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java @@ -3,9 +3,11 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.zalando.logbook.DefaultLogbook.SimpleCorrelation; import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; import java.io.IOException; +import java.time.Clock; import static java.time.Duration.ZERO; import static org.hamcrest.MatcherAssert.assertThat; @@ -28,7 +30,7 @@ void shouldDefaultToTraceLevelForActivation() throws IOException { final Logger logger = mock(Logger.class); final HttpLogWriter unit = new DefaultHttpLogWriter(logger); - unit.isActive(mock(RawHttpRequest.class)); + unit.isActive(); verify(logger).isTraceEnabled(); } @@ -38,7 +40,7 @@ void shouldDefaultToTraceLevelForLoggingRequests() throws IOException { final Logger logger = mock(Logger.class); final HttpLogWriter unit = new DefaultHttpLogWriter(logger); - unit.writeRequest(new SimplePrecorrelation<>("1", "foo", MockHttpRequest.create())); + unit.write(new SimplePrecorrelation(Clock.systemUTC()), "foo"); verify(logger).trace("foo"); } @@ -48,8 +50,7 @@ void shouldDefaultToTraceLevelForLoggingResponses() throws IOException { final Logger logger = mock(Logger.class); final HttpLogWriter unit = new DefaultHttpLogWriter(logger); - unit.writeResponse(new DefaultLogbook.SimpleCorrelation<>("1", ZERO, "foo", "bar", - MockHttpRequest.create(), MockHttpResponse.create())); + unit.write(new SimpleCorrelation("1", ZERO), "bar"); verify(logger).trace("bar"); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java deleted file mode 100644 index a8505b6a4..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.Optional; - -import static java.util.Optional.empty; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public final class DefaultLogbookFactoryTest { - - @Test - void shouldDefaultToAlwaysTruePredicate() throws IOException { - final HttpLogWriter writer = mock(HttpLogWriter.class); - when(writer.isActive(any())).thenReturn(true); - - final Logbook logbook = Logbook.builder() - .writer(writer) - .build(); - - final Optional correlator = logbook.write(MockRawHttpRequest.create()); - - assertThat(correlator, is(not(empty()))); - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java index 5350bf1dc..3e3159248 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java @@ -22,31 +22,26 @@ public final class DefaultLogbookTest { - private final HttpLogFormatter formatter = mock(HttpLogFormatter.class); - private final HttpLogWriter writer = mock(HttpLogWriter.class); @SuppressWarnings("unchecked") - private final Predicate predicate = mock(Predicate.class); - private final RawRequestFilter rawRequestFilter = mock(RawRequestFilter.class); - private final RawResponseFilter rawResponseFilter = mock(RawResponseFilter.class); + private final Predicate predicate = mock(Predicate.class); private final HeaderFilter headerFilter = mock(HeaderFilter.class); private final QueryFilter queryFilter = mock(QueryFilter.class); private final BodyFilter bodyFilter = mock(BodyFilter.class); + private final Sink sink = mock(Sink.class); private final Logbook unit = Logbook.builder() .condition(predicate) - .rawRequestFilter(rawRequestFilter) - .rawResponseFilter(rawResponseFilter) .queryFilter(queryFilter) .headerFilter(headerFilter) .bodyFilter(bodyFilter) - .formatter(formatter) - .writer(writer) + .strategy(new DefaultStrategy()) + .sink(sink) .build(); - private final RawHttpRequest request = mock(RawHttpRequest.class, withSettings(). - defaultAnswer(delegateTo(MockRawHttpRequest.create()))); + private final HttpRequest request = mock(HttpRequest.class, withSettings(). + defaultAnswer(delegateTo(MockHttpRequest.create()))); - private final RawHttpResponse response = MockRawHttpResponse.create(); + private final HttpResponse response = MockHttpResponse.create(); private static Answer delegateTo(final Object delegate) { return invocation -> @@ -54,82 +49,62 @@ private static Answer delegateTo(final Object delegate) { } @BeforeEach - public void defaultBehaviour() throws IOException { - when(writer.isActive(any())).thenReturn(true); + public void defaultBehaviour() { + when(sink.isActive()).thenReturn(true); when(predicate.test(any())).thenReturn(true); - when(rawRequestFilter.filter(any())).thenAnswer(invocation -> invocation.getArgument(0)); - when(rawResponseFilter.filter(any())).thenAnswer(invocation -> invocation.getArgument(0)); } @Test - void shouldNotReturnCorrelatorIfInactiveWriter() throws IOException { - when(writer.isActive(any())).thenReturn(false); + void shouldNotWriteIfSinkInactive() throws IOException { + when(sink.isActive()).thenReturn(false); - final Optional correlator = unit.write(request); + unit.process(request).write().process(response).write(); - assertThat(correlator, hasFeature("present", Optional::isPresent, is(false))); + verify(sink, never()).write(any(), any()); + verify(sink, never()).write(any(), any(), any()); + verify(sink, never()).writeBoth(any(), any(), any()); } @Test - void shouldNotReturnCorrelatorIfPredicateTestsFalse() throws IOException { - when(writer.isActive(any())).thenReturn(true); + void shouldNotWriteIfPredicateTestsFalse() throws IOException { when(predicate.test(any())).thenReturn(false); - final Optional correlator = unit.write(request); + unit.process(request).write().process(response).write(); - assertThat(correlator, hasFeature("present", Optional::isPresent, is(false))); + verify(sink, never()).write(any(), any()); + verify(sink, never()).write(any(), any(), any()); + verify(sink, never()).writeBoth(any(), any(), any()); } @Test void shouldNeverRetrieveBodyIfInactiveWriter() throws IOException { - when(writer.isActive(any())).thenReturn(false); + when(sink.isActive()).thenReturn(false); - unit.write(request); + unit.process(request).write(); verify(request, never()).withBody(); } - @Test - void shouldFilterRawRequest() throws IOException { - unit.write(request); - - verify(rawRequestFilter).filter(request); - } - - @Test - void shouldFilterRawResponse() throws IOException { - unit.write(request).get().write(response); - - verify(rawResponseFilter).filter(response); - } - @Test void shouldFilterRequest() throws IOException { - final Correlator correlator = unit.write(request).get(); + unit.process(request).write(); - correlator.write(response); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(sink).write(any(), captor.capture()); + final HttpRequest request = captor.getValue(); - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(formatter).format(captor.capture()); - final Precorrelation precorrelation = captor.getValue(); - - assertThat(precorrelation.getRequest(), instanceOf(FilteredHttpRequest.class)); + assertThat(request, instanceOf(FilteredHttpRequest.class)); } @Test void shouldFilterResponse() throws IOException { - final Correlator correlator = unit.write(request).get(); - - correlator.write(response); + unit.process(request).write().process(response).write(); - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Correlation.class); - verify(formatter).format(captor.capture()); - final Correlation correlation = captor.getValue(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(sink).write(any(), any(), captor.capture()); + final HttpResponse response = captor.getValue(); - assertThat(correlation.getRequest(), instanceOf(FilteredHttpRequest.class)); - assertThat(correlation.getResponse(), instanceOf(FilteredHttpResponse.class)); + assertThat(response, instanceOf(FilteredHttpResponse.class)); } } diff --git a/logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriter.java b/logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriter.java deleted file mode 100644 index e69ab22b4..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriter.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.zalando.logbook; - -import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; - -import java.io.IOException; - -// proof of concept -final class DelayedResponseLogWriter implements HttpLogWriter { - - private final HttpLogWriter delegate; - - DelayedResponseLogWriter(final HttpLogWriter delegate) { - this.delegate = delegate; - } - - @Override - public void writeRequest(final Precorrelation precorrelation) throws IOException { - // do nothing - } - - @Override - public void writeResponse(final Correlation correlation) throws IOException { - final HttpResponse response = correlation.getOriginalResponse(); - - if (response.getStatus() >= 400) { - // delayed request logging until we have the response at hand - delegate.writeRequest(new SimplePrecorrelation<>(correlation.getId(), - correlation.getRequest(), correlation.getOriginalRequest())); - delegate.writeResponse(correlation); - } - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriterTest.java deleted file mode 100644 index b1ae0737d..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/DelayedResponseLogWriterTest.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -import java.io.IOException; - -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.never; -import static org.mockito.Mockito.verify; - -public final class DelayedResponseLogWriterTest { - - private final HttpLogWriter delegate = Mockito.mock(HttpLogWriter.class); - private final HttpLogWriter unit = new DelayedResponseLogWriter(delegate); - - @Test - void shouldDelayRequestLogging() throws IOException { - final Logbook logbook = Logbook.builder().writer(unit).build(); - - final Correlator correlator = logbook.write(MockRawHttpRequest.create()).orElseThrow(AssertionError::new); - - verify(delegate, never()).writeRequest(any()); - - correlator.write(MockRawHttpResponse.create().withStatus(404)); - - verify(delegate).writeRequest(any()); - verify(delegate).writeResponse(any()); - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java index 26fea4174..013949547 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java @@ -7,6 +7,7 @@ import java.io.IOException; import static com.jayway.jsonassert.JsonAssert.with; +import static java.time.Clock.systemUTC; import static java.time.Duration.ZERO; import static java.time.Duration.ofMillis; import static java.util.Collections.singletonList; @@ -39,7 +40,7 @@ void shouldLogRequest() throws IOException { .withContentType("application/xml") .withBodyAsString("test"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); with(json) .assertThat("$.origin", is("remote")) @@ -62,7 +63,7 @@ void shouldLogRequestWithoutHeaders() throws IOException { .withPath("/test") .withBodyAsString("Hello, world!"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); with(json) .assertThat("$", not(hasKey("headers"))); @@ -77,7 +78,7 @@ void shouldLogRequestWithoutContentType() throws IOException { .withPath("/test") .withBodyAsString("Hello"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(correlationId, systemUTC()), request); with(json) .assertThat("$.origin", is("remote")) @@ -97,7 +98,7 @@ void shouldLogRequestWithoutBody() throws IOException { .withContentType("") .withBodyAsString(""); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$", not(hasKey("body"))); @@ -110,7 +111,7 @@ void shouldEmbedJsonRequestBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body.name", is("Bob")); @@ -123,7 +124,7 @@ void shouldNotEmbedInvalidJsonRequestBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\"name\":\"Bob\"};"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"};")); @@ -136,7 +137,7 @@ void shouldNotEmbedInvalidButProbableJsonRequestBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\"name\":\"Bob\"\n;}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"\n;}")); @@ -149,7 +150,7 @@ void shouldNotEmbedReplacedJsonRequestBody() throws IOException { .withContentType("application/json") .withBodyAsString(""); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("")); @@ -162,7 +163,7 @@ void shouldEmbedCompactedJsonRequestBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\n \"name\": \"Bob\"\n}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); assertThat(json, containsString("{\"name\":\"Bob\"}")); } @@ -174,7 +175,7 @@ void shouldEmbedCustomJsonRequestBody() throws IOException { .withContentType("application/custom+json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body.name", is("Bob")); @@ -187,7 +188,7 @@ void shouldEmbedCustomJsonWithParametersRequestBody() throws IOException { .withContentType("application/custom+json; version=2") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body.name", is("Bob")); @@ -200,7 +201,7 @@ void shouldNotEmbedCustomTextXmlRequestBody() throws IOException { .withContentType("text/xml") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"}")); @@ -213,7 +214,7 @@ void shouldNotEmbedInvalidContentTypeRequestBody() throws IOException { .withContentType("x;y/z") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"}")); @@ -226,7 +227,7 @@ void shouldNotEmbedCustomTextJsonRequestBody() throws IOException { .withContentType("text/custom+json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"}")); @@ -239,7 +240,7 @@ void shouldNotEmbedNonJsonRequestBody() throws IOException { .withContentType("application/not-json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"}")); @@ -251,7 +252,7 @@ void shouldEmbedEmptyJsonRequestBodyAsEmptyString() throws IOException { final HttpRequest request = MockHttpRequest.create() .withContentType("application/json"); - final String json = unit.format(new SimplePrecorrelation<>(correlationId, request, request)); + final String json = unit.format(new SimplePrecorrelation(systemUTC()), request); with(json) .assertThat("$.body", is(emptyString())); @@ -268,8 +269,7 @@ void shouldLogResponse() throws IOException { .withContentType("application/xml") .withBodyAsString("true"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ofMillis(125), request, response, - request, response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ofMillis(125)), response); with(json) .assertThat("$.origin", is("local")) @@ -289,8 +289,7 @@ void shouldLogResponseWithoutHeaders() throws IOException { final HttpRequest request = MockHttpRequest.create(); final HttpResponse response = create(); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, - request, response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$", not(hasKey("headers"))); @@ -304,8 +303,7 @@ void shouldLogResponseWithoutBody() throws IOException { final HttpResponse response = create() .withBodyAsString(""); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$", not(hasKey("body"))); @@ -319,8 +317,7 @@ void shouldEmbedJsonResponseBodyAsIs() throws IOException { .withContentType("application/json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, - request, response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$.body.name", is("Bob")); @@ -334,8 +331,7 @@ void shouldCompactEmbeddedJsonResponseBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\n \"name\": \"Bob\"\n}"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); assertThat(json, containsString("{\"name\":\"Bob\"}")); } @@ -348,8 +344,7 @@ void shouldEmbedCustomJsonResponseBodyAsIs() throws IOException { .withContentType("application/custom+json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$.body.name", is("Bob")); @@ -363,8 +358,7 @@ void shouldNotEmbedCustomTextJsonResponseBodyAsIs() throws IOException { .withContentType("text/custom+json") .withBodyAsString("{\"name\":\"Bob\"}"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"}")); @@ -377,8 +371,7 @@ void shouldEmbedJsonResponseBodyAsNullIfEmpty() throws IOException { final HttpResponse response = create() .withContentType("application/json"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$.body", is(emptyString())); @@ -392,8 +385,7 @@ void shouldNotEmbedInvalidJsonResponseBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\"name\":\"Bob\"};"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"};")); @@ -407,8 +399,7 @@ void shouldNotEmbedInvalidButProbableJsonResponseBody() throws IOException { .withContentType("application/json") .withBodyAsString("{\"name\":\"Bob\"\n;};"); - final String json = unit.format(new SimpleCorrelation<>(correlationId, ZERO, request, response, request, - response)); + final String json = unit.format(new SimpleCorrelation(correlationId, ZERO), response); with(json) .assertThat("$.body", is("{\"name\":\"Bob\"\n;};")); diff --git a/logbook-core/src/test/java/org/zalando/logbook/RawRequestFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/RawRequestFiltersTest.java deleted file mode 100644 index 4bf523fe4..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/RawRequestFiltersTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public final class RawRequestFiltersTest { - - @Test - void shouldReplaceImageBodyByDefault() throws IOException { - final RawRequestFilter filter = RawRequestFilters.defaultValue(); - - final RawHttpRequest request = filter.filter(MockRawHttpRequest.create() - .withContentType("image/png") - .withBodyAsString("this is an image")); - - assertThat(request.getContentType(), is("image/png")); - assertThat(request.withBody().getContentType(), is("image/png")); - assertThat(request.withBody().getBody(), is("".getBytes(UTF_8))); - assertThat(request.withBody().getBodyAsString(), is("")); - } - - @Test - void shouldNotReplaceTextByDefault() throws IOException { - final RawRequestFilter filter = RawRequestFilters.defaultValue(); - - final RawHttpRequest request = filter.filter(MockRawHttpRequest.create() - .withContentType("text/plain") - .withBodyAsString("Hello")); - - assertThat(request.withBody().getBody(), is("Hello".getBytes(UTF_8))); - assertThat(request.withBody().getBodyAsString(), is("Hello")); - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/RawResponseFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/RawResponseFiltersTest.java deleted file mode 100644 index 805e715fb..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/RawResponseFiltersTest.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -public final class RawResponseFiltersTest { - - @Test - void shouldReplaceImageBodyByDefault() throws IOException { - final RawResponseFilter filter = RawResponseFilters.defaultValue(); - - final RawHttpResponse response = filter.filter(MockRawHttpResponse.create() - .withContentType("image/png") - .withBodyAsString("this is an image")); - - assertThat(response.getContentType(), is("image/png")); - assertThat(response.withBody().getContentType(), is("image/png")); - assertThat(response.withBody().getBody(), is("".getBytes(UTF_8))); - assertThat(response.withBody().getBodyAsString(), is("")); - } - - @Test - void shouldNotReplaceTextBodyByDefault() throws IOException { - final RawResponseFilter filter = RawResponseFilters.defaultValue(); - - final RawHttpResponse response = filter.filter(MockRawHttpResponse.create() - .withContentType("text/plain") - .withBodyAsString("Hello")); - - assertThat(response.withBody().getBody(), is("Hello".getBytes(UTF_8))); - assertThat(response.withBody().getBodyAsString(), is("Hello")); - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java new file mode 100644 index 000000000..b0e020ba0 --- /dev/null +++ b/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java @@ -0,0 +1,43 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public final class RequestFiltersTest { + + @Test + void shouldReplaceImageBodyByDefault() throws IOException { + final RequestFilter filter = RequestFilters.defaultValue(); + + final HttpRequest request = filter.filter(MockHttpRequest.create() + .withContentType("image/png") + .withBodyAsString("this is an image")); + + request.withBody(); + + assertThat(request.getContentType(), is("image/png")); + assertThat(request.getContentType(), is("image/png")); + assertThat(request.getBody(), is("".getBytes(UTF_8))); + assertThat(request.getBodyAsString(), is("")); + } + + @Test + void shouldNotReplaceTextByDefault() throws IOException { + final RequestFilter filter = RequestFilters.defaultValue(); + + final HttpRequest request = filter.filter(MockHttpRequest.create() + .withContentType("text/plain") + .withBodyAsString("Hello")); + + request.withBody(); + + assertThat(request.getBody(), is("Hello".getBytes(UTF_8))); + assertThat(request.getBodyAsString(), is("Hello")); + } + +} diff --git a/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java new file mode 100644 index 000000000..2bc3cac9d --- /dev/null +++ b/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java @@ -0,0 +1,43 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; + +public final class ResponseFiltersTest { + + @Test + void shouldReplaceImageBodyByDefault() throws IOException { + final ResponseFilter filter = ResponseFilters.defaultValue(); + + final HttpResponse response = filter.filter(MockHttpResponse.create() + .withContentType("image/png") + .withBodyAsString("this is an image")); + + response.withBody(); + + assertThat(response.getContentType(), is("image/png")); + assertThat(response.getContentType(), is("image/png")); + assertThat(response.getBody(), is("".getBytes(UTF_8))); + assertThat(response.getBodyAsString(), is("")); + } + + @Test + void shouldNotReplaceTextBodyByDefault() throws IOException { + final ResponseFilter filter = ResponseFilters.defaultValue(); + + final HttpResponse response = filter.filter(MockHttpResponse.create() + .withContentType("text/plain") + .withBodyAsString("Hello")); + + response.withBody(); + + assertThat(response.getBody(), is("Hello".getBytes(UTF_8))); + assertThat(response.getBodyAsString(), is("Hello")); + } + +} diff --git a/logbook-core/src/test/java/org/zalando/logbook/SeparateRequestResponseLogWriter.java b/logbook-core/src/test/java/org/zalando/logbook/SeparateRequestResponseLogWriter.java deleted file mode 100644 index 621c0dd15..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/SeparateRequestResponseLogWriter.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.zalando.logbook; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; - -// proof of concept -final class SeparateRequestResponseLogWriter implements HttpLogWriter { - - private final Logger incoming = LoggerFactory.getLogger("http.incoming"); - private final Logger outgoing = LoggerFactory.getLogger("http.outgoing"); - - // TODO overwrite isActive? - - @Override - public void writeRequest(final Precorrelation precorrelation) throws IOException { - choose(precorrelation.getOriginalRequest()).trace(precorrelation.getRequest()); - } - - @Override - public void writeResponse(final Correlation correlation) throws IOException { - choose(correlation.getOriginalResponse()).trace(correlation.getResponse()); - } - - private Logger choose(final HttpRequest request) { - return request.getOrigin() == Origin.REMOTE ? incoming : outgoing; - } - - private Logger choose(final HttpResponse response) { - return response.getOrigin() == Origin.LOCAL ? incoming : outgoing; - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/SplunkHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/SplunkHttpLogFormatterTest.java index 00dbb9b97..da1006802 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/SplunkHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/SplunkHttpLogFormatterTest.java @@ -5,6 +5,7 @@ import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; import java.io.IOException; +import java.time.Clock; import java.time.Duration; import static java.time.Duration.ZERO; @@ -38,7 +39,7 @@ void shouldLogCompleteRequest() throws IOException { .withContentType("application/xml") .withBodyAsString("test"); - final String format = unit.format(correlation(correlationId, request)); + final String format = unit.format(correlation(correlationId), request); assertThat(format, stringContainsInOrder( "origin=remote", @@ -64,7 +65,7 @@ void shouldLogRequestWithoutHeaders() throws IOException { .withPath("/test") .withBodyAsString("Hello, world!"); - final String format = unit.format(correlation(correlationId, request)); + final String format = unit.format(correlation(correlationId), request); assertThat(format, not(containsString("headers"))); } @@ -79,7 +80,7 @@ void shouldLogRequestWithoutContentType() throws IOException { .withPath("/test") .withBodyAsString("Hello"); - final String format = unit.format(correlation(correlationId, request)); + final String format = unit.format(correlation(correlationId), request); assertThat(format, stringContainsInOrder( "origin=remote", @@ -98,7 +99,7 @@ void shouldLogRequestWithoutBody() throws IOException { final String correlationId = "ac5c3dc2-682a-11e5-83cd-10ddb1ee7671"; final HttpRequest request = MockHttpRequest.create().withBodyAsString(""); - final String format = unit.format(correlation(correlationId, request)); + final String format = unit.format(correlation(correlationId), request); assertThat(format, not(containsString("body"))); } @@ -106,7 +107,6 @@ void shouldLogRequestWithoutBody() throws IOException { @Test void shouldLogCompleteResponse() throws IOException { final String correlationId = "53de2640-677d-11e5-bc84-10ddb1ee7671"; - final HttpRequest request = MockHttpRequest.create(); final HttpResponse response = create() .withProtocolVersion("HTTP/1.0") .withOrigin(LOCAL) @@ -114,7 +114,7 @@ void shouldLogCompleteResponse() throws IOException { .withContentType("application/xml") .withBodyAsString("true"); - final String format = unit.format(correlation(correlationId, ofMillis(125), request, response)); + final String format = unit.format(correlation(correlationId, ofMillis(125)), response); assertThat(format, stringContainsInOrder( "origin=local", @@ -133,10 +133,9 @@ void shouldLogCompleteResponse() throws IOException { @Test void shouldLogResponseWithoutHeaders() throws IOException { final String correlationId = "f53ceee2-682a-11e5-a63e-10ddb1ee7671"; - final HttpRequest request = MockHttpRequest.create(); final HttpResponse response = create(); - final String format = unit.format(correlation(correlationId, ZERO, request, response)); + final String format = unit.format(correlation(correlationId, ZERO), response); assertThat(format, not(containsString("headers"))); } @@ -144,28 +143,21 @@ void shouldLogResponseWithoutHeaders() throws IOException { @Test void shouldLogResponseWithoutBody() throws IOException { final String correlationId = "f238536c-682a-11e5-9bdd-10ddb1ee7671"; - final HttpRequest request = MockHttpRequest.create(); final HttpResponse response = create() .withBodyAsString(""); - final String format = unit.format(correlation(correlationId, ZERO, request, response)); + final String format = unit.format(correlation(correlationId, ZERO), response); assertThat(format, not(containsString("body"))); } - private SimplePrecorrelation correlation( - final String correlationId, - final HttpRequest request - ) { - return new SimplePrecorrelation<>(correlationId, request, request); + private SimplePrecorrelation correlation(final String correlationId) { + return new SimplePrecorrelation(correlationId, Clock.systemUTC()); } - private SimpleCorrelation correlation( + private SimpleCorrelation correlation( final String correlationId, - final Duration duration, - final HttpRequest request, - final HttpResponse response - ) { - return new SimpleCorrelation<>(correlationId, duration, request, response, request, response); + final Duration duration) { + return new SimpleCorrelation(correlationId, duration); } -} \ No newline at end of file +} diff --git a/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java index b03befbda..14f1805f0 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java @@ -2,10 +2,12 @@ import net.jcip.annotations.NotThreadSafe; import org.junit.jupiter.api.Test; +import org.zalando.logbook.DefaultLogbook.SimpleCorrelation; import org.zalando.logbook.DefaultLogbook.SimplePrecorrelation; import java.io.IOException; import java.io.PrintStream; +import java.time.Clock; import static java.time.Duration.ZERO; import static org.hamcrest.MatcherAssert.assertThat; @@ -17,11 +19,11 @@ public final class StreamHttpLogWriterTest { @Test - void shouldBeActiveByDefault() throws IOException { + void shouldBeActiveByDefault() { final PrintStream stream = mock(PrintStream.class); final HttpLogWriter unit = new StreamHttpLogWriter(stream); - assertThat(unit.isActive(mock(RawHttpRequest.class)), is(true)); + assertThat(unit.isActive(), is(true)); } @Test @@ -29,7 +31,7 @@ void shouldLogRequestToStream() throws IOException { final PrintStream stream = mock(PrintStream.class); final HttpLogWriter unit = new StreamHttpLogWriter(stream); - unit.writeRequest(new SimplePrecorrelation<>("1", "foo", MockHttpRequest.create())); + unit.write(new SimplePrecorrelation(Clock.systemUTC()), "foo"); verify(stream).println("foo"); } @@ -39,8 +41,7 @@ void shouldLogResponseToStream() throws IOException { final PrintStream stream = mock(PrintStream.class); final HttpLogWriter unit = new StreamHttpLogWriter(stream); - unit.writeResponse(new DefaultLogbook.SimpleCorrelation<>("1", ZERO, "foo", "bar", MockHttpRequest.create(), - MockHttpResponse.create())); + unit.write(new SimpleCorrelation("1", ZERO), "bar"); verify(stream).println("bar"); } @@ -54,7 +55,7 @@ void shouldRequestToStdoutByDefault() throws IOException { try { final HttpLogWriter unit = new StreamHttpLogWriter(); - unit.writeRequest(new SimplePrecorrelation<>("1", "foo", MockHttpRequest.create())); + unit.write(new SimplePrecorrelation(Clock.systemUTC()), "foo"); verify(stream).println("foo"); } finally { @@ -71,8 +72,7 @@ void shouldResponseToStdoutByDefault() throws IOException { try { final HttpLogWriter unit = new StreamHttpLogWriter(); - unit.writeResponse(new DefaultLogbook.SimpleCorrelation<>("1", ZERO, "foo", "bar", MockHttpRequest.create(), - MockHttpResponse.create())); + unit.write(new SimpleCorrelation("1", ZERO), "bar"); verify(stream).println("bar"); } finally { diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/Attributes.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/Attributes.java index eab0610d1..1bbb09118 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/Attributes.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/Attributes.java @@ -4,7 +4,7 @@ final class Attributes { - static final String CORRELATOR = Logbook.class.getName() + ".CORRELATOR"; + static final String STAGE = Logbook.class.getName() + ".STAGE"; private Attributes() { diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java index e3548ffc0..0cee3c090 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java @@ -8,7 +8,6 @@ import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpRequest; import java.io.IOException; import java.net.URI; @@ -20,7 +19,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.http.util.EntityUtils.toByteArray; -final class LocalRequest implements RawHttpRequest, org.zalando.logbook.HttpRequest { +final class LocalRequest implements org.zalando.logbook.HttpRequest { private final HttpRequest request; private final URI originalRequestUri; @@ -123,20 +122,28 @@ public Charset getCharset() { @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } @Override public org.zalando.logbook.HttpRequest withBody() throws IOException { - if (request instanceof HttpEntityEnclosingRequest) { - final HttpEntityEnclosingRequest request = (HttpEntityEnclosingRequest) this.request; - this.body = toByteArray(request.getEntity()); - request.setEntity(new ByteArrayEntity(body)); - } else { - this.body = new byte[0]; + if (body == null) { + if (request instanceof HttpEntityEnclosingRequest) { + final HttpEntityEnclosingRequest original = (HttpEntityEnclosingRequest) request; + this.body = toByteArray(original.getEntity()); + original.setEntity(new ByteArrayEntity(body)); + } else { + return withoutBody(); + } } return this; } + @Override + public org.zalando.logbook.HttpRequest withoutBody() { + this.body = new byte[0]; + return this; + } + } 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 df714b772..fe7eafab4 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 @@ -5,11 +5,10 @@ import org.apache.http.nio.protocol.HttpAsyncResponseConsumer; import org.apache.http.protocol.HttpContext; import org.apiguardian.api.API; -import org.zalando.logbook.Correlator; +import org.zalando.logbook.Logbook.ResponseProcessingStage; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.Optional; import static org.apiguardian.api.API.Status.EXPERIMENTAL; @@ -36,19 +35,19 @@ public void responseReceived(final HttpResponse response) throws IOException, Ht @Override public void responseCompleted(final HttpContext context) { - findCorrelator(context).ifPresent(correlator -> { - try { - correlator.write(new RemoteResponse(response)); - } catch (final IOException e) { - throw new UncheckedIOException(e); - } - }); + final ResponseProcessingStage stage = find(context); + + try { + stage.process(new RemoteResponse(response)).write(); + } catch (final IOException e) { + throw new UncheckedIOException(e); + } delegate().responseCompleted(context); } - private Optional findCorrelator(final HttpContext context) { - return Optional.ofNullable(context.getAttribute(Attributes.CORRELATOR)).map(Correlator.class::cast); + private ResponseProcessingStage find(final HttpContext context) { + return (ResponseProcessingStage) context.getAttribute(Attributes.STAGE); } } diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpRequestInterceptor.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpRequestInterceptor.java index 3b544a79c..d1f69aa3e 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpRequestInterceptor.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LogbookHttpRequestInterceptor.java @@ -1,16 +1,13 @@ package org.zalando.logbook.httpclient; -import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.protocol.HttpContext; import org.apiguardian.api.API; -import org.zalando.logbook.Correlator; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseProcessingStage; import java.io.IOException; -import java.util.Optional; -import java.util.function.Consumer; import static org.apiguardian.api.API.Status.STABLE; @@ -24,14 +21,10 @@ public LogbookHttpRequestInterceptor(final Logbook logbook) { } @Override - public void process(final HttpRequest httpRequest, final HttpContext context) throws HttpException, IOException { + public void process(final HttpRequest httpRequest, final HttpContext context) throws IOException { final LocalRequest request = new LocalRequest(httpRequest); - final Optional correlator = logbook.write(request); - correlator.ifPresent(writeCorrelator(context)); - } - - private Consumer writeCorrelator(final HttpContext context) { - return correlator -> context.setAttribute(Attributes.CORRELATOR, correlator); + final ResponseProcessingStage stage = logbook.process(request).write(); + context.setAttribute(Attributes.STAGE, stage); } } 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 605ba3f8c..defd45550 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 @@ -5,13 +5,11 @@ import org.apache.http.nio.client.HttpAsyncClient; import org.apache.http.protocol.HttpContext; import org.apiguardian.api.API; -import org.zalando.logbook.Correlator; +import org.zalando.logbook.Logbook.ResponseProcessingStage; import java.io.IOException; -import java.util.Optional; import static org.apiguardian.api.API.Status.STABLE; -import static org.zalando.fauxpas.FauxPas.throwingConsumer; /** * A response interceptor for synchronous responses. For {@link HttpAsyncClient} support, please use @@ -25,13 +23,12 @@ public final class LogbookHttpResponseInterceptor implements HttpResponseInterce @Override public void process(final HttpResponse original, final HttpContext context) throws IOException { - final Optional correlator = findCorrelator(context); - - correlator.ifPresent(throwingConsumer(c -> c.write(new RemoteResponse(original)))); + final ResponseProcessingStage stage = find(context); + stage.process(new RemoteResponse(original)).write(); } - private Optional findCorrelator(final HttpContext context) { - return Optional.ofNullable(context.getAttribute(Attributes.CORRELATOR)).map(Correlator.class::cast); + private ResponseProcessingStage find(final HttpContext context) { + return (ResponseProcessingStage) context.getAttribute(Attributes.STAGE); } } 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 976ac4b47..8a2ba8dbc 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 @@ -6,7 +6,6 @@ import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpResponse; import javax.annotation.Nullable; import java.io.IOException; @@ -18,7 +17,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.http.util.EntityUtils.toByteArray; -final class RemoteResponse implements RawHttpResponse, org.zalando.logbook.HttpResponse { +final class RemoteResponse implements org.zalando.logbook.HttpResponse { private final HttpResponse response; private byte[] body; @@ -73,27 +72,34 @@ public Charset getCharset() { @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } @Override public org.zalando.logbook.HttpResponse withBody() throws IOException { - @Nullable final HttpEntity originalEntity = response.getEntity(); + if (body == null) { + @Nullable final HttpEntity entity = response.getEntity(); - if (originalEntity == null) { - this.body = new byte[0]; - return this; - } + if (entity == null) { + return withoutBody(); + } else { + this.body = toByteArray(entity); - this.body = toByteArray(originalEntity); + final ByteArrayEntity copy = new ByteArrayEntity(body); + copy.setChunked(entity.isChunked()); + copy.setContentEncoding(entity.getContentEncoding()); + copy.setContentType(entity.getContentType()); - ByteArrayEntity byteArrayEntity = new ByteArrayEntity(body); - byteArrayEntity.setChunked(originalEntity.isChunked()); - byteArrayEntity.setContentEncoding(originalEntity.getContentEncoding()); - byteArrayEntity.setContentType(originalEntity.getContentType()); + response.setEntity(copy); + } + } - response.setEntity(byteArrayEntity); + return this; + } + @Override + public RemoteResponse withoutBody() { + this.body = new byte[0]; return this; } diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java index ce102a6b8..522ae32b6 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java @@ -39,8 +39,8 @@ public abstract class AbstractHttpTest { protected final HttpLogWriter writer = mock(HttpLogWriter.class); @BeforeEach - public void defaultBehaviour() throws IOException { - when(writer.isActive(any())).thenReturn(true); + public void defaultBehaviour() { + when(writer.isActive()).thenReturn(true); } @Test @@ -72,22 +72,21 @@ void shouldLogRequestWithBody() throws IOException, ExecutionException, Interrup assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureRequest() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Precorrelation.class); - verify(writer).writeRequest(captor.capture()); - return captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + return captor.getValue(); } @Test void shouldNotLogRequestIfInactive() throws IOException, ExecutionException, InterruptedException { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); sendAndReceive(); - verify(writer, never()).writeRequest(any()); + verify(writer, never()).write(any(Precorrelation.class), any()); } @Test @@ -122,22 +121,21 @@ void shouldLogResponseWithBody() throws IOException, ExecutionException, Interru assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureResponse() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Correlation.class); - verify(writer).writeResponse(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Correlation.class), captor.capture()); + return captor.getValue(); } @Test void shouldNotLogResponseIfInactive() throws IOException, ExecutionException, InterruptedException { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); sendAndReceive(); - verify(writer, never()).writeResponse(any()); + verify(writer, never()).write(any(Correlation.class), any()); } private void sendAndReceive() throws InterruptedException, ExecutionException, IOException { diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java index b4ec4f13e..8b8b561fa 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java @@ -11,7 +11,6 @@ import org.apache.http.entity.StringEntity; import org.apache.http.message.BasicHttpRequest; import org.junit.jupiter.api.Test; -import org.zalando.logbook.BaseHttpRequest; import java.io.IOException; import java.net.URI; @@ -29,7 +28,6 @@ public final class LocalRequestTest { - private HttpRequest get(final String uri) { return new HttpGet(uri); } @@ -53,14 +51,14 @@ void shouldResolveLocalhost() { void shouldRetrieveAbsoluteRequestUri() { final LocalRequest unit = unit(get("http://localhost/")); - assertThat(unit, hasFeature("request uri", BaseHttpRequest::getRequestUri, hasToString("http://localhost/"))); + assertThat(unit, hasFeature("request uri", org.zalando.logbook.HttpRequest::getRequestUri, hasToString("http://localhost/"))); } @Test void shouldTrimQueryStringFromRequestUri() { final LocalRequest unit = unit(get("http://localhost/?limit=1")); - assertThat(unit, hasFeature("request uri", BaseHttpRequest::getRequestUri, + assertThat(unit, hasFeature("request uri", org.zalando.logbook.HttpRequest::getRequestUri, hasToString("http://localhost/?limit=1"))); } @@ -68,21 +66,21 @@ void shouldTrimQueryStringFromRequestUri() { void shouldParseQueryStringIntoQueryParameters() { final LocalRequest unit = unit(get("http://localhost/?limit=1")); - assertThat(unit, hasFeature("query parameters", BaseHttpRequest::getQuery, is("limit=1"))); + assertThat(unit, hasFeature("query parameters", org.zalando.logbook.HttpRequest::getQuery, is("limit=1"))); } @Test void shouldRetrieveAbsoluteRequestUriForWrappedRequests() throws URISyntaxException { final LocalRequest unit = unit(wrap(get("http://localhost/"))); - assertThat(unit, hasFeature("request uri", BaseHttpRequest::getRequestUri, hasToString("http://localhost/"))); + assertThat(unit, hasFeature("request uri", org.zalando.logbook.HttpRequest::getRequestUri, hasToString("http://localhost/"))); } @Test void shouldRetrieveRelativeUriForNonHttpUriRequests() { final LocalRequest unit = unit(new BasicHttpRequest("GET", "http://localhost/")); - assertThat(unit, hasFeature("request uri", BaseHttpRequest::getRequestUri, hasToString("http://localhost/"))); + assertThat(unit, hasFeature("request uri", org.zalando.logbook.HttpRequest::getRequestUri, hasToString("http://localhost/"))); } private HttpRequestWrapper wrap(final HttpRequest delegate) throws URISyntaxException { 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 7327cd18d..b9cfaaa2b 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 @@ -13,8 +13,11 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.zalando.logbook.Correlator; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseProcessingStage; +import org.zalando.logbook.Logbook.ResponseWritingStage; import javax.annotation.Nullable; import java.io.IOException; @@ -31,11 +34,12 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public final class LogbookHttpAsyncResponseConsumerTest extends AbstractHttpTest { private final Logbook logbook = Logbook.builder() - .writer(writer) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); private final CloseableHttpAsyncClient client = HttpAsyncClientBuilder.create() @@ -44,7 +48,7 @@ public final class LogbookHttpAsyncResponseConsumerTest extends AbstractHttpTest @SuppressWarnings("unchecked") private final FutureCallback callback = mock(FutureCallback.class); - private final Correlator correlator = mock(Correlator.class); + private final ResponseProcessingStage stage = mock(ResponseProcessingStage.class); @BeforeEach void start() { @@ -81,9 +85,13 @@ void shouldWrapIOException() throws IOException { final HttpAsyncResponseConsumer unit = new LogbookHttpAsyncResponseConsumer<>(createConsumer()); final BasicHttpContext context = new BasicHttpContext(); - context.setAttribute(Attributes.CORRELATOR, correlator); + context.setAttribute(Attributes.STAGE, stage); - doThrow(new IOException()).when(correlator).write(any()); + final ResponseWritingStage last = mock(ResponseWritingStage.class); + + when(stage.process(any())).thenReturn(last); + + doThrow(new IOException()).when(last).write(); assertThrows(UncheckedIOException.class, () -> unit.responseCompleted(context)); 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 5fd09b280..2d2674345 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 @@ -7,6 +7,8 @@ import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.junit.jupiter.api.AfterEach; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.Logbook; import javax.annotation.Nullable; @@ -20,7 +22,7 @@ public final class LogbookHttpInterceptorsTest extends AbstractHttpTest { private final Logbook logbook = Logbook.builder() - .writer(writer) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); private final CloseableHttpClient client = HttpClientBuilder.create() diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/Attributes.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/Attributes.java deleted file mode 100644 index aae70cd0d..000000000 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/Attributes.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.zalando.logbook.jaxrs; - -import org.zalando.logbook.Logbook; - -final class Attributes { - - static final String CORRELATOR = Logbook.class.getName() + ".CORRELATOR"; - static final String REQUEST = Logbook.class.getName() + ".REQUEST"; - static final String RESPONSE = Logbook.class.getName() + ".RESPONSE"; - - private Attributes() { - - } - -} diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalRequest.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalRequest.java index 0fe95d0e0..d2feca095 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalRequest.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalRequest.java @@ -2,7 +2,6 @@ import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpRequest; import javax.annotation.Nullable; import javax.ws.rs.client.ClientRequestContext; @@ -13,17 +12,14 @@ import static java.util.Optional.ofNullable; -final class LocalRequest implements HttpRequest, RawHttpRequest { +final class LocalRequest implements HttpRequest { private final ClientRequestContext context; - private final TeeOutputStream stream; + private TeeOutputStream stream; private byte[] body; public LocalRequest(final ClientRequestContext context) { - // TODO now we need to buffer needlessly if we never actually need the body - this.stream = new TeeOutputStream(context.getEntityStream()); - context.setEntityStream(stream); this.context = context; } @@ -91,12 +87,31 @@ public Charset getCharset() { @Override public HttpRequest withBody() { + if (stream == null) { + this.stream = new TeeOutputStream(context.getEntityStream()); + context.setEntityStream(stream); + } + + return this; + } + + @Override + public HttpRequest withoutBody() { + if (stream != null) { + context.setEntityStream(stream.getOriginal()); + this.stream = null; + } + return this; } @Override public byte[] getBody() { if (body == null) { + if (stream == null) { + return new byte[0]; + } + this.body = stream.toByteArray(); } diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalResponse.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalResponse.java index 8bab8df03..1e3d2b4e3 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalResponse.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LocalResponse.java @@ -2,7 +2,6 @@ import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpResponse; import javax.annotation.Nullable; import javax.ws.rs.container.ContainerResponseContext; @@ -11,16 +10,14 @@ import java.util.Map; import java.util.Objects; -final class LocalResponse implements HttpResponse, RawHttpResponse { +final class LocalResponse implements HttpResponse { private final ContainerResponseContext context; - private final TeeOutputStream stream; + private TeeOutputStream stream; private byte[] body; public LocalResponse(final ContainerResponseContext context) { - this.stream = new TeeOutputStream(context.getEntityStream()); - context.setEntityStream(stream); this.context = context; } @@ -58,12 +55,31 @@ public Charset getCharset() { @Override public HttpResponse withBody() { + if (stream == null) { + this.stream = new TeeOutputStream(context.getEntityStream()); + context.setEntityStream(stream); + } + + return this; + } + + @Override + public HttpResponse withoutBody() { + if (stream != null) { + context.setEntityStream(stream.getOriginal()); + this.stream = null; + } + return this; } @Override public byte[] getBody() { if (body == null) { + if (stream == null) { + return new byte[0]; + } + this.body = stream.toByteArray(); } diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java index fa18ccb9b..7abc41bf0 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java @@ -1,8 +1,8 @@ package org.zalando.logbook.jaxrs; -import org.zalando.logbook.Correlator; import org.zalando.logbook.Logbook; -import org.zalando.logbook.RawHttpRequest; +import org.zalando.logbook.Logbook.RequestWritingStage; +import org.zalando.logbook.Logbook.ResponseProcessingStage; import javax.ws.rs.ConstrainedTo; import javax.ws.rs.RuntimeType; @@ -16,12 +16,9 @@ import javax.ws.rs.ext.WriterInterceptorContext; import java.io.IOException; import java.util.Optional; -import java.util.function.BiConsumer; import java.util.function.Function; import static org.zalando.fauxpas.FauxPas.throwingConsumer; -import static org.zalando.logbook.jaxrs.Attributes.CORRELATOR; -import static org.zalando.logbook.jaxrs.Attributes.REQUEST; @Provider @ConstrainedTo(RuntimeType.CLIENT) @@ -35,12 +32,12 @@ public LogbookClientFilter(final Logbook logbook) { @Override public void filter(final ClientRequestContext context) throws IOException { - final RawHttpRequest request = new LocalRequest(context); + final RequestWritingStage stage = logbook.process(new LocalRequest(context)); if (context.hasEntity()) { - context.setProperty(REQUEST, request); + context.setProperty("write-request", stage); } else { - logRequest(context::setProperty, request); + context.setProperty("process-response", stage.write()); } } @@ -48,23 +45,16 @@ public void filter(final ClientRequestContext context) throws IOException { public void aroundWriteTo(final WriterInterceptorContext context) throws IOException, WebApplicationException { context.proceed(); - read(context::getProperty, REQUEST, RawHttpRequest.class) - .ifPresent(throwingConsumer(request -> - logRequest(context::setProperty, request))); - } - - private void logRequest(final BiConsumer context, final RawHttpRequest request) - throws IOException { - logbook.write(request) - .ifPresent(correlator -> - context.accept(CORRELATOR, correlator)); + read(context::getProperty, "write-request", RequestWritingStage.class) + .ifPresent(throwingConsumer(stage -> + context.setProperty("process-response", stage.write()))); } @Override public void filter(final ClientRequestContext request, final ClientResponseContext response) { - read(request::getProperty, CORRELATOR, Correlator.class) + read(request::getProperty, "process-response", ResponseProcessingStage.class) .ifPresent(throwingConsumer(correlator -> - correlator.write(new RemoteResponse(response)))); + correlator.process(new RemoteResponse(response)).write())); } private static Optional read(final Function provider, final String name, diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java index 0a33442e7..83ebacc4f 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java @@ -1,8 +1,9 @@ package org.zalando.logbook.jaxrs; -import org.zalando.logbook.Correlator; +import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; -import org.zalando.logbook.RawHttpResponse; +import org.zalando.logbook.Logbook.ResponseProcessingStage; +import org.zalando.logbook.Logbook.ResponseWritingStage; import javax.ws.rs.ConstrainedTo; import javax.ws.rs.RuntimeType; @@ -18,8 +19,6 @@ import java.util.function.Function; import static org.zalando.fauxpas.FauxPas.throwingConsumer; -import static org.zalando.logbook.jaxrs.Attributes.CORRELATOR; -import static org.zalando.logbook.jaxrs.Attributes.RESPONSE; @Provider @ConstrainedTo(RuntimeType.SERVER) @@ -33,32 +32,29 @@ public LogbookServerFilter(final Logbook logbook) { @Override public void filter(final ContainerRequestContext context) throws IOException { - logbook.write(new RemoteRequest(context)) - .ifPresent(correlator -> - context.setProperty(CORRELATOR, correlator)); + final RemoteRequest request = new RemoteRequest(context); + final ResponseProcessingStage stage = logbook.process(request).write(); + context.setProperty("process-response", stage); } @Override - public void filter(final ContainerRequestContext request, final ContainerResponseContext response) { - final RawHttpResponse rawHttpResponse = new LocalResponse(response); + public void filter(final ContainerRequestContext request, final ContainerResponseContext context) { + final HttpResponse response = new LocalResponse(context); - if (response.hasEntity()) { - request.setProperty(RESPONSE, rawHttpResponse); - } else { - read(request::getProperty, CORRELATOR, Correlator.class) - .ifPresent(throwingConsumer(correlator -> - correlator.write(rawHttpResponse))); - } + read(request::getProperty, "process-response", ResponseProcessingStage.class) + .ifPresent(context.hasEntity() ? + throwingConsumer(stage -> + request.setProperty("write-response", stage.process(response))) : + throwingConsumer(stage -> + stage.process(response).write())); } @Override public void aroundWriteTo(final WriterInterceptorContext context) throws IOException { context.proceed(); - read(context::getProperty, CORRELATOR, Correlator.class) - .ifPresent(throwingConsumer(correlator -> - read(context::getProperty, RESPONSE, RawHttpResponse.class) - .ifPresent(throwingConsumer(correlator::write)))); + read(context::getProperty, "write-response", ResponseWritingStage.class) + .ifPresent(throwingConsumer(ResponseWritingStage::write)); } private static Optional read(final Function provider, final String name, diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java index 41fc26a3a..f415040b7 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java @@ -2,7 +2,6 @@ import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpRequest; import javax.annotation.Nullable; import javax.ws.rs.container.ContainerRequestContext; @@ -15,7 +14,7 @@ import static java.util.Optional.ofNullable; -final class RemoteRequest implements HttpRequest, RawHttpRequest { +final class RemoteRequest implements HttpRequest { private final ContainerRequestContext context; private byte[] body; @@ -89,14 +88,23 @@ public Charset getCharset() { @Override public HttpRequest withBody() throws IOException { - this.body = ByteStreams.toByteArray(context.getEntityStream()); - context.setEntityStream(new ByteArrayInputStream(body)); + if (body == null) { + this.body = ByteStreams.toByteArray(context.getEntityStream()); + context.setEntityStream(new ByteArrayInputStream(body)); + } + + return this; + } + + @Override + public HttpRequest withoutBody() { + this.body = new byte[0]; return this; } @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteResponse.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteResponse.java index fda2e5e06..2f2d817bd 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteResponse.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteResponse.java @@ -2,7 +2,6 @@ import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpResponse; import javax.annotation.Nullable; import javax.ws.rs.client.ClientResponseContext; @@ -13,7 +12,7 @@ import java.util.Map; import java.util.Objects; -final class RemoteResponse implements HttpResponse, RawHttpResponse { +final class RemoteResponse implements HttpResponse { private final ClientResponseContext context; private byte[] body; @@ -56,14 +55,22 @@ public Charset getCharset() { @Override public HttpResponse withBody() throws IOException { - this.body = ByteStreams.toByteArray(context.getEntityStream()); - context.setEntityStream(new ByteArrayInputStream(body)); + if (body == null) { + this.body = ByteStreams.toByteArray(context.getEntityStream()); + context.setEntityStream(new ByteArrayInputStream(body)); + } + return this; + } + + @Override + public HttpResponse withoutBody() { + this.body = new byte[0]; return this; } @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/TeeOutputStream.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/TeeOutputStream.java index 9d5f6e8a5..598b67c53 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/TeeOutputStream.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/TeeOutputStream.java @@ -40,6 +40,10 @@ public void close() throws IOException { copy.close(); } + OutputStream getOriginal() { + return original; + } + byte[] toByteArray() { return copy.toByteArray(); } diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java index 19a19d654..2d579c39d 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java @@ -11,11 +11,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.zalando.logbook.Correlation; -import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Sink; import org.zalando.logbook.jaxrs.testing.support.TestModel; import org.zalando.logbook.jaxrs.testing.support.TestWebService; @@ -43,7 +42,7 @@ import static org.zalando.logbook.BodyReplacers.stream; import static org.zalando.logbook.Origin.LOCAL; import static org.zalando.logbook.Origin.REMOTE; -import static org.zalando.logbook.RawRequestFilters.replaceBody; +import static org.zalando.logbook.RequestFilters.replaceBody; /** * This test starts in in-memory server with a Logbook server filter. The test @@ -57,8 +56,8 @@ */ public final class JerseyClientAndServerTest extends JerseyTest { - private HttpLogWriter clientWriter; - private HttpLogWriter serverWriter; + private Sink client; + private Sink server; public JerseyClientAndServerTest() { forceSet(TestProperties.CONTAINER_PORT, "0"); @@ -67,15 +66,15 @@ public JerseyClientAndServerTest() { @Override protected Application configure() { // jersey calls this method within the constructor before our fields are initialized... WTF - this.clientWriter = mock(HttpLogWriter.class); - this.serverWriter = mock(HttpLogWriter.class); + this.client = mock(Sink.class); + this.server = mock(Sink.class); return new ResourceConfig(TestWebService.class) .register(new LogbookServerFilter( Logbook.builder() // do not replace multi-part form bodies, which is the default - .rawRequestFilter(replaceBody(stream())) - .writer(serverWriter) + .requestFilter(replaceBody(stream())) + .sink(server) .build())) .register(MultiPartFeature.class); } @@ -84,15 +83,15 @@ protected Application configure() { public void beforeEach() throws Exception { super.setUp(); - when(clientWriter.isActive(any())).thenReturn(true); - when(serverWriter.isActive(any())).thenReturn(true); + when(client.isActive()).thenReturn(true); + when(server.isActive()).thenReturn(true); getClient() .register(new LogbookClientFilter( Logbook.builder() // do not replace multi-part form bodies, which is the default - .rawRequestFilter(replaceBody(stream())) - .writer(clientWriter) + .requestFilter(replaceBody(stream())) + .sink(client) .build())) .register(MultiPartFeature.class); } @@ -298,21 +297,23 @@ public void putJsonPayloadReturningJsonPayload() throws IOException { } private RoundTrip getRoundTrip() throws IOException { - final Correlation clientCorrelation = capture(clientWriter); - final Correlation serverCorrelation = capture(serverWriter); - return new RoundTrip( - clientCorrelation.getOriginalRequest(), - clientCorrelation.getOriginalResponse(), - serverCorrelation.getOriginalRequest(), - serverCorrelation.getOriginalResponse() + captureRequest(client), + captureResponse(client), + captureRequest(server), + captureResponse(server) ); } - private static Correlation capture(final HttpLogWriter writer) throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = - ArgumentCaptor.forClass(Correlation.class); - verify(writer).writeResponse(captor.capture()); + private static HttpRequest captureRequest(final Sink sink) throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(sink).write(any(), captor.capture()); + return captor.getValue(); + } + + private static HttpResponse captureResponse(final Sink sink) throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(sink).write(any(), any(), captor.capture()); return captor.getValue(); } diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java index 6ebba4e18..53c676295 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java @@ -6,7 +6,6 @@ import okio.Buffer; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpRequest; import javax.annotation.Nullable; import java.io.IOException; @@ -19,7 +18,7 @@ import static okhttp3.HttpUrl.defaultPort; import static okhttp3.RequestBody.create; -final class LocalRequest implements RawHttpRequest, HttpRequest { +final class LocalRequest implements HttpRequest { private Request request; private byte[] body; @@ -98,23 +97,29 @@ private Optional contentType() { @Override public HttpRequest withBody() throws IOException { - @Nullable final RequestBody body = request.body(); - if (body == null) { - this.body = new byte[0]; - } else { - final byte[] bytes = bytes(body); + @Nullable final RequestBody entity = request.body(); - this.request = request.newBuilder() - .method(request.method(), create(body.contentType(), bytes)) - .build(); + if (entity == null) { + return withoutBody(); + } else { + this.body = bytes(entity); - this.body = bytes; + this.request = request.newBuilder() + .method(request.method(), create(entity.contentType(), body)) + .build(); + } } return this; } + @Override + public HttpRequest withoutBody() { + this.body = new byte[0]; + return this; + } + private static byte[] bytes(final RequestBody body) throws IOException { final Buffer buffer = new Buffer(); body.writeTo(buffer); @@ -127,7 +132,7 @@ Request toRequest() { @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LogbookInterceptor.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LogbookInterceptor.java index 2c337f8d4..8dd839bfc 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LogbookInterceptor.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LogbookInterceptor.java @@ -3,14 +3,12 @@ import okhttp3.Interceptor; import okhttp3.Response; import org.apiguardian.api.API; -import org.zalando.logbook.Correlator; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseProcessingStage; import java.io.IOException; -import java.util.Optional; import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.zalando.fauxpas.FauxPas.throwingConsumer; @API(status = EXPERIMENTAL) public final class LogbookInterceptor implements Interceptor { @@ -24,11 +22,9 @@ public LogbookInterceptor(final Logbook logbook) { @Override public Response intercept(final Chain chain) throws IOException { final LocalRequest request = new LocalRequest(chain.request()); - final Optional correlator = logbook.write(request); - + final ResponseProcessingStage stage = logbook.process(request).write(); final RemoteResponse response = new RemoteResponse(chain.proceed(request.toRequest())); - - correlator.ifPresent(throwingConsumer(c -> c.write(response))); + stage.process(response).write(); return response.toResponse(); } diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java index 3a267e684..f90993373 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java @@ -5,7 +5,6 @@ import okhttp3.ResponseBody; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpResponse; import java.io.IOException; import java.nio.charset.Charset; @@ -18,7 +17,7 @@ import static java.util.Objects.requireNonNull; import static okhttp3.ResponseBody.create; -final class RemoteResponse implements RawHttpResponse, HttpResponse { +final class RemoteResponse implements HttpResponse { private Response response; private byte[] body; @@ -65,30 +64,37 @@ private Optional contentType() { @Override public HttpResponse withBody() throws IOException { - final ResponseBody body = requireNonNull(response.body(), "Body is never null for normal responses"); + if (body == null) { + final ResponseBody entity = requireNonNull(response.body(), "Body is never null for normal responses"); - if (body.contentLength() == 0L) { - this.body = new byte[0]; - } else { - final byte[] bytes = body.bytes(); + if (entity.contentLength() == 0L) { + return withoutBody(); + } else { + this.body = entity.bytes(); - this.response = response.newBuilder() - .body(create(body.contentType(), bytes)) - .build(); + this.response = response.newBuilder() + .body(create(entity.contentType(), body)) + .build(); - this.body = bytes; + } } return this; } + @Override + public RemoteResponse withoutBody() { + this.body = new byte[0]; + return this; + } + public Response toResponse() { return response; } @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java index 8e6a058ed..8c5305b81 100644 --- a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java +++ b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java @@ -9,6 +9,8 @@ import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; import org.zalando.logbook.Correlation; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; @@ -39,7 +41,10 @@ final class LogbookInterceptorTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() - .writer(writer) + .sink(new DefaultSink( + new DefaultHttpLogFormatter(), + writer + )) .build(); private final OkHttpClient client = new OkHttpClient.Builder() @@ -49,8 +54,8 @@ final class LogbookInterceptorTest { private final ClientDriver driver = new ClientDriverFactory().createClientDriver(); @BeforeEach - void defaultBehaviour() throws IOException { - when(writer.isActive(any())).thenReturn(true); + void defaultBehaviour() { + when(writer.isActive()).thenCallRealMethod(); } @Test @@ -87,20 +92,20 @@ void shouldLogRequestWithBody() throws IOException { @SuppressWarnings("unchecked") private String captureRequest() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Precorrelation.class); - verify(writer).writeRequest(captor.capture()); - return captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + return captor.getValue(); } @Test void shouldNotLogRequestIfInactive() throws IOException { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); sendAndReceive(); - verify(writer, never()).writeRequest(any()); + verify(writer, never()).write(any(Precorrelation.class), any()); } @Test @@ -138,20 +143,20 @@ void shouldLogResponseWithBody() throws IOException { @SuppressWarnings("unchecked") private String captureResponse() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Correlation.class); - verify(writer).writeResponse(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Correlation.class), captor.capture()); + return captor.getValue(); } @Test void shouldNotLogResponseIfInactive() throws IOException { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); sendAndReceive(); - verify(writer, never()).writeResponse(any()); + verify(writer, never()).write(any(Correlation.class), any()); } private void sendAndReceive() throws IOException { diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java index bb40e8ba1..d99590615 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java @@ -2,7 +2,6 @@ import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpResponse; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletResponse; @@ -20,17 +19,15 @@ import static java.nio.charset.StandardCharsets.UTF_8; -final class LocalResponse extends HttpServletResponseWrapper implements RawHttpResponse, HttpResponse { +final class LocalResponse extends HttpServletResponseWrapper implements HttpResponse { private final String protocolVersion; - private boolean withBody; private Tee tee; LocalResponse(final HttpServletResponse response, final String protocolVersion) { super(response); this.protocolVersion = protocolVersion; - this.withBody = true; } @Override @@ -60,52 +57,40 @@ public Charset getCharset() { } @Override - public HttpResponse withBody() { - assertWithBody(); + public HttpResponse withBody() throws IOException { + if (tee == null) { + this.tee = new Tee(super.getOutputStream()); + } return this; } @Override - public void withoutBody() { - assertWithBody(); - withBody = false; + public HttpResponse withoutBody() { + this.tee = null; + return this; } @Override public ServletOutputStream getOutputStream() throws IOException { - if (withBody) { - return tee().getOutputStream(); - } else { + if (tee == null) { return super.getOutputStream(); + } else { + return tee.getOutputStream(); } } @Override public PrintWriter getWriter() throws IOException { - if (withBody) { - return tee().getWriter(this::getCharset); - } else { + if (tee == null) { return super.getWriter(); + } else { + return tee.getWriter(this::getCharset); } } @Override - public byte[] getBody() throws IOException { - assertWithBody(); - return tee().getBytes(); - } - - private void assertWithBody() { - if (!withBody) { - throw new IllegalStateException("Response is without body"); - } - } - - private Tee tee() throws IOException { - if (tee == null) { - tee = new Tee(super.getOutputStream()); - } - return tee; + public byte[] getBody() { + return tee == null ? new byte[0] : tee.getBytes(); } private static class Tee { diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java index fcbd66f72..f2fbd34c3 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java @@ -1,26 +1,25 @@ package org.zalando.logbook.servlet; -import org.zalando.logbook.Correlator; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.RawHttpRequest; -import org.zalando.logbook.RawRequestFilter; +import org.zalando.logbook.Logbook.ResponseProcessingStage; +import org.zalando.logbook.Logbook.ResponseWritingStage; +import org.zalando.logbook.RequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Optional; -import java.util.function.Consumer; import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE; import static org.zalando.logbook.servlet.Attributes.CORRELATOR; final class NormalStrategy implements Strategy { - private final RawRequestFilter filter; + private final RequestFilter filter; - NormalStrategy(final RawRequestFilter filter) { + NormalStrategy(final RequestFilter filter) { this.filter = filter; } @@ -29,51 +28,45 @@ public void doFilter(final Logbook logbook, final HttpServletRequest httpRequest final HttpServletResponse httpResponse, final FilterChain chain) throws ServletException, IOException { final RemoteRequest request = new RemoteRequest(httpRequest); - final Optional correlator = logRequestIfNecessary(logbook, request); + final ResponseProcessingStage stage = logRequest(logbook, request); - if (correlator.isPresent()) { - final String protocolVersion = request.getProtocolVersion(); - final LocalResponse response = new LocalResponse(httpResponse, protocolVersion); + final String protocolVersion = request.getProtocolVersion(); + final LocalResponse response = new LocalResponse(httpResponse, protocolVersion); - chain.doFilter(request, response); - logResponse(correlator.get(), request, response); - } else { - chain.doFilter(httpRequest, httpResponse); - } + final ResponseWritingStage writingStage = stage.process(response); + + chain.doFilter(request, response); + logResponse(writingStage, request, response); } - private Optional logRequestIfNecessary(final Logbook logbook, + private ResponseProcessingStage logRequest(final Logbook logbook, final RemoteRequest request) throws IOException { if (isFirstRequest(request)) { - final Optional correlator = logbook.write(skipBodyIfErrorDispatch(request)); - correlator.ifPresent(writeCorrelator(request)); - return correlator; + final ResponseProcessingStage stage = logbook.process(skipBodyIfErrorDispatch(request)).write(); + request.setAttribute(CORRELATOR, stage); + return stage; } else { return readCorrelator(request); } } - private RawHttpRequest skipBodyIfErrorDispatch(final RemoteRequest request) { + private HttpRequest skipBodyIfErrorDispatch(final RemoteRequest request) { if (request.getAttribute(ERROR_EXCEPTION_TYPE) == null) { return request; } return filter.filter(request); } - private Consumer writeCorrelator(final RemoteRequest request) { - return correlator -> request.setAttribute(CORRELATOR, correlator); - } - - private Optional readCorrelator(final RemoteRequest request) { - return Optional.ofNullable(request.getAttribute(CORRELATOR)).map(Correlator.class::cast); + private ResponseProcessingStage readCorrelator(final RemoteRequest request) { + return (ResponseProcessingStage) request.getAttribute(CORRELATOR); } - private void logResponse(final Correlator correlator, final RemoteRequest request, + private void logResponse(final ResponseWritingStage correlator, final RemoteRequest request, final LocalResponse response) throws IOException { if (isLastRequest(request)) { response.getWriter().flush(); - correlator.write(response); + correlator.write(); } } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java index 9a3a6920e..6771d5bf3 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java @@ -3,7 +3,6 @@ import lombok.SneakyThrows; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpRequest; import javax.activation.MimeType; import javax.annotation.Nullable; @@ -26,16 +25,10 @@ import static java.util.Collections.list; import static java.util.stream.Collectors.joining; -final class RemoteRequest extends HttpServletRequestWrapper implements RawHttpRequest, HttpRequest { - - private static final byte[] EMPTY_BODY = new byte[0]; +final class RemoteRequest extends HttpServletRequestWrapper implements HttpRequest { private final FormRequestMode formRequestMode = FormRequestMode.fromProperties(); - /** - * Null until we successfully intercepted it. - */ - @Nullable private byte[] body; RemoteRequest(final HttpServletRequest request) { @@ -97,20 +90,29 @@ public Charset getCharset() { @Override public HttpRequest withBody() throws IOException { - if (isFormRequest()) { - switch (formRequestMode) { - case PARAMETER: - this.body = reconstructBodyFromParameters(); - return this; - case OFF: - this.body = EMPTY_BODY; - return this; - default: - break; + if (body == null) { + if (isFormRequest()) { + switch (formRequestMode) { + case PARAMETER: + this.body = reconstructBodyFromParameters(); + return this; + case OFF: + this.body = new byte[0]; + return this; + default: + break; + } } + + this.body = ByteStreams.toByteArray(super.getInputStream()); } - body = ByteStreams.toByteArray(super.getInputStream()); + return this; + } + + @Override + public HttpRequest withoutBody() { + this.body = new byte[0]; return this; } @@ -157,6 +159,6 @@ public BufferedReader getReader() throws IOException { @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java index 8b7f3baed..76fe871c1 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java @@ -1,24 +1,22 @@ package org.zalando.logbook.servlet; -import org.zalando.logbook.Correlator; import org.zalando.logbook.Logbook; -import org.zalando.logbook.RawRequestFilter; +import org.zalando.logbook.Logbook.ResponseProcessingStage; +import org.zalando.logbook.RequestFilter; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.Optional; -import static org.zalando.fauxpas.FauxPas.throwingConsumer; import static org.zalando.logbook.servlet.Attributes.CORRELATOR; final class SecurityStrategy implements Strategy { - private final RawRequestFilter filter; + private final RequestFilter filter; - SecurityStrategy(final RawRequestFilter filter) { + SecurityStrategy(final RequestFilter filter) { this.filter = filter; } @@ -32,26 +30,26 @@ public void doFilter(final Logbook logbook, final HttpServletRequest httpRequest chain.doFilter(request, response); - if (isUnauthorizedOrForbibben(response)) { - final Optional correlator; + if (isUnauthorizedOrForbidden(response)) { + final ResponseProcessingStage correlator; if (isFirstRequest(request)) { - correlator = logbook.write(filter.filter(request)); + correlator = logbook.process(filter.filter(request)).write(); } else { correlator = readCorrelator(request); } - correlator.ifPresent(throwingConsumer(c -> c.write(response))); + correlator.process(response).write(); } } - private boolean isUnauthorizedOrForbibben(final HttpServletResponse response) { + private boolean isUnauthorizedOrForbidden(final HttpServletResponse response) { final int status = response.getStatus(); return status == 401 || status == 403; } - private Optional readCorrelator(final RemoteRequest request) { - return Optional.ofNullable(request.getAttribute(CORRELATOR)).map(Correlator.class::cast); + private ResponseProcessingStage readCorrelator(final RemoteRequest request) { + return (ResponseProcessingStage) request.getAttribute(CORRELATOR); } } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java index b7783c551..c4f4971c4 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java @@ -11,7 +11,7 @@ import java.io.IOException; import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.zalando.logbook.RawRequestFilters.replaceBody; +import static org.zalando.logbook.RequestFilters.replaceBody; @API(status = MAINTAINED) public interface Strategy { diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java index 3dc9cea9f..2b870a749 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java @@ -5,16 +5,14 @@ import org.mockito.ArgumentCaptor; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.zalando.logbook.BaseHttpMessage; -import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.HttpMessage; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; -import org.zalando.logbook.Precorrelation; import javax.servlet.DispatcherType; import java.io.IOException; @@ -50,16 +48,15 @@ public final class AsyncDispatchTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test @@ -87,7 +84,7 @@ void shouldFormatAsyncResponse() throws Exception { final HttpResponse response = interceptResponse(); assertThat(response, hasFeature("status", HttpResponse::getStatus, is(200))); - assertThat(response, hasFeature("headers", BaseHttpMessage::getHeaders, + assertThat(response, hasFeature("headers", HttpMessage::getHeaders, hasEntry("Content-Type", singletonList("application/json;charset=UTF-8")))); assertThat(response, hasFeature("content type", HttpResponse::getContentType, is("application/json;charset=UTF-8"))); @@ -106,17 +103,15 @@ private String getBodyAsString(final HttpMessage message) { } private HttpRequest interceptRequest() throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(formatter).format(captor.capture()); - return captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(formatter).format(any(), captor.capture()); + return captor.getValue(); } private HttpResponse interceptResponse() throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Correlation.class); - verify(formatter).format(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(formatter).format(any(), captor.capture()); + return captor.getValue(); } } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java index 3bda94639..f8adf37cc 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java @@ -5,15 +5,14 @@ import org.mockito.ArgumentCaptor; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.HttpMessage; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; -import org.zalando.logbook.Precorrelation; import javax.servlet.DispatcherType; import java.io.IOException; @@ -42,16 +41,15 @@ public final class ErrorDispatchTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test @@ -77,17 +75,15 @@ private String getBodyAsString(final HttpMessage message) { } private HttpRequest interceptRequest() throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(formatter).format(captor.capture()); - return captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(formatter).format(any(), captor.capture()); + return captor.getValue(); } private HttpResponse interceptResponse() throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Correlation.class); - verify(formatter).format(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(formatter).format(any(), captor.capture()); + return captor.getValue(); } } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java index 70ea7eaa2..e35fd29c6 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java @@ -6,16 +6,14 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.zalando.logbook.BaseHttpMessage; -import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.HttpMessage; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; -import org.zalando.logbook.Precorrelation; import java.io.IOException; @@ -50,16 +48,15 @@ public final class FormattingTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test @@ -100,7 +97,7 @@ void shouldFormatResponse() throws Exception { final HttpResponse response = interceptResponse(); assertThat(response, hasFeature("status", HttpResponse::getStatus, is(200))); - assertThat(response, hasFeature("headers", BaseHttpMessage::getHeaders, + assertThat(response, hasFeature("headers", HttpMessage::getHeaders, hasEntry("Content-Type", singletonList("application/json;charset=UTF-8")))); assertThat(response, hasFeature("content type", HttpResponse::getContentType, is("application/json;charset=UTF-8"))); @@ -149,17 +146,15 @@ private String getBodyAsString(final HttpMessage message) { } private HttpRequest interceptRequest() throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(formatter).format(captor.capture()); - return captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(formatter).format(any(), captor.capture()); + return captor.getValue(); } private HttpResponse interceptResponse() throws IOException { - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Correlation.class); - verify(formatter).format(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(formatter).format(any(), captor.capture()); + return captor.getValue(); } } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java index e9b3adf21..def61d4dd 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java @@ -22,17 +22,19 @@ protected HttpLogFormatter delegate() { } @Override - public String format(final Precorrelation precorrelation) throws IOException { - return delegate().format(precorrelation); + public String format(final Precorrelation precorrelation, final HttpRequest request) throws IOException { + return delegate().format(precorrelation, request); } @Override - public String format(final Correlation correlation) throws IOException { - return delegate().format(correlation); + public String format(final Correlation correlation, HttpResponse response) + throws IOException { + return delegate().format(correlation, response); } @Override public String toString() { return delegate().toString(); } + } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java index 6b6c7c0c7..be22780be 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java @@ -13,7 +13,6 @@ import static org.hamcrest.Matchers.nullValue; import static org.junit.jupiter.api.Assertions.assertNotSame; import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -40,6 +39,7 @@ public void write(final int b) { @Test void shouldUseSameBody() throws IOException { unit.getOutputStream().write("test".getBytes()); + unit.withBody(); final byte[] body1 = unit.getBody(); final byte[] body2 = unit.getBody(); @@ -108,11 +108,9 @@ void shouldDelegateGetWriter() throws IOException { } @Test - void shouldNotAllowWithBodyAfterWithoutBody() throws IOException { - assertThrows(IllegalStateException.class, () -> { - unit.withoutBody(); - unit.withBody(); - }); + void shouldAllowWithBodyAfterWithoutBody() throws IOException { + unit.withoutBody(); + unit.withBody(); } @Test diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java index 7d3916629..4879c3bc2 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java @@ -5,26 +5,23 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import org.mockito.ArgumentCaptor; -import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.zalando.logbook.Correlation; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; import org.zalando.logbook.JsonHttpLogFormatter; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; import javax.servlet.Filter; -import javax.servlet.ServletException; -import java.io.IOException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; -import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -47,8 +44,7 @@ public final class MultiFilterSecurityTest { private final SecurityFilter securityFilter = spy(new SecurityFilter()); private final Logbook logbook = Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build(); private final Filter firstFilter = spy(new SpyableFilter(new LogbookFilter(logbook, Strategy.SECURITY))); @@ -62,90 +58,38 @@ public final class MultiFilterSecurityTest { .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test - @SuppressWarnings("unchecked") void shouldFormatAuthorizedRequestOnce() throws Exception { mvc.perform(get("/api/sync")); - verify(formatter).format(any(Precorrelation.class)); + verify(formatter).format(any(), any(HttpRequest.class)); } @Test - @SuppressWarnings("unchecked") void shouldFormatAuthorizedResponseOnce() throws Exception { mvc.perform(get("/api/sync")); - verify(formatter).format(any(Correlation.class)); + verify(formatter).format(any(), any(HttpResponse.class)); } @Test void shouldLogAuthorizedRequestOnce() throws Exception { mvc.perform(get("/api/sync")); - verify(writer).writeRequest(any()); + verify(writer).write(any(Precorrelation.class), any()); } @Test void shouldLogAuthorizedResponseOnce() throws Exception { mvc.perform(get("/api/sync")); - verify(writer).writeResponse(any()); - } - - @Test - void shouldBufferAuthorizedRequestOnlyOnce() throws Exception { - mvc.perform(get("/api/read-byte") - .contentType(MediaType.TEXT_PLAIN) - .content("Hello, world!")).andReturn(); - - final RemoteRequest firstRequest = getRequest(securityFilter); - final RemoteRequest secondRequest = getRequest(controller); - - assertNull(firstRequest.getBody()); - assertThat(secondRequest.getBody().length, is(greaterThan(0))); - } - - @Test - void shouldBufferAuthorizedResponseTwice() throws Exception { - mvc.perform(get("/api/read-bytes") - .contentType(MediaType.TEXT_PLAIN) - .content("Hello, world!")).andReturn(); - - final LocalResponse firstResponse = getResponse(securityFilter); - final LocalResponse secondResponse = getResponse(controller); - - assertThat(firstResponse.getBody().length, is(greaterThan(0))); - assertThat(secondResponse.getBody().length, is(greaterThan(0))); - } - - private RemoteRequest getRequest(final Filter filter) throws IOException, ServletException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(RemoteRequest.class); - verify(filter).doFilter(captor.capture(), any(), any()); - return captor.getValue(); - } - - private RemoteRequest getRequest(final ExampleController controller) throws IOException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(RemoteRequest.class); - verify(controller).readByte(captor.capture(), any()); - return captor.getValue(); - } - - private LocalResponse getResponse(final Filter filter) throws IOException, ServletException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(LocalResponse.class); - verify(filter).doFilter(any(), captor.capture(), any()); - return captor.getValue(); - } - - private LocalResponse getResponse(final ExampleController controller) throws IOException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(LocalResponse.class); - verify(controller).readBytes(any(), captor.capture()); - return captor.getValue(); + verify(writer).write(any(Precorrelation.class), any()); } @ParameterizedTest @@ -156,7 +100,7 @@ void shouldFormatUnauthorizedRequestOnce(final int status) throws Exception { mvc.perform(get("/api/sync")); - verify(formatter).format(any(Precorrelation.class)); + verify(formatter).format(any(), any(HttpRequest.class)); } @ParameterizedTest @@ -167,7 +111,7 @@ void shouldFormatUnauthorizedResponseOnce(final int status) throws Exception { mvc.perform(get("/api/sync")); - verify(formatter).format(any(Correlation.class)); + verify(formatter).format(any(), any(HttpResponse.class)); } @ParameterizedTest @@ -177,7 +121,7 @@ void shouldLogUnauthorizedRequestOnce(final int status) throws Exception { mvc.perform(get("/api/sync")); - verify(writer).writeRequest(any()); + verify(writer).write(any(Precorrelation.class), any()); } @ParameterizedTest @@ -187,7 +131,7 @@ void shouldLogUnauthorizedResponseOnce(final int status) throws Exception { mvc.perform(get("/api/sync")); - verify(writer).writeResponse(any()); + verify(writer).write(any(Precorrelation.class), any()); } @ParameterizedTest @@ -198,24 +142,23 @@ void shouldNotLogRequestBodyForUnauthorizedRequests(final int status) throws Exc mvc.perform(post("/api/sync") .content("Hello, world!")); - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(writer).writeRequest(captor.capture()); - final Precorrelation precorrelation = captor.getValue(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String request = captor.getValue(); - assertThat(precorrelation.getRequest(), not(containsString("Hello, world"))); + assertThat(request, not(containsString("Hello, world"))); } @ParameterizedTest @ValueSource(ints = {401, 403}) void shouldNotLogUnauthorizedRequest(final int status) throws Exception { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); securityFilter.setStatus(status); mvc.perform(get("/api/sync")); - verify(writer, never()).writeRequest(any()); - verify(writer, never()).writeResponse(any()); + verify(writer, never()).write(any(Precorrelation.class), any()); + verify(writer, never()).write(any(Correlation.class), any()); } @Test diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java index 20be670d1..3410541dc 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java @@ -8,8 +8,11 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; @@ -38,8 +41,7 @@ public final class MultiFilterTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build(); private final Filter firstFilter = spy(new SpyableFilter(new LogbookFilter(logbook))); @@ -52,10 +54,10 @@ public final class MultiFilterTest { .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test @@ -63,7 +65,7 @@ public void setUp() throws IOException { void shouldFormatRequestTwice() throws Exception { mvc.perform(get("/api/sync")); - verify(formatter, times(2)).format(any(Precorrelation.class)); + verify(formatter, times(2)).format(any(), any(HttpRequest.class)); } @Test @@ -71,21 +73,21 @@ void shouldFormatRequestTwice() throws Exception { void shouldFormatResponseTwice() throws Exception { mvc.perform(get("/api/sync")); - verify(formatter, times(2)).format(any(Correlation.class)); + verify(formatter, times(2)).format(any(), any(HttpResponse.class)); } @Test void shouldLogRequestTwice() throws Exception { mvc.perform(get("/api/sync")); - verify(writer, times(2)).writeRequest(any()); + verify(writer, times(2)).write(any(Precorrelation.class), any()); } @Test void shouldLogResponseTwice() throws Exception { mvc.perform(get("/api/sync")); - verify(writer, times(2)).writeResponse(any()); + verify(writer, times(2)).write(any(Correlation.class), any()); } @Test diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java index f0c3b0d60..9db834dd2 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java @@ -6,13 +6,15 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; import javax.servlet.DispatcherType; -import java.io.IOException; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; @@ -34,16 +36,15 @@ public final class SkipTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); } @@ -52,8 +53,8 @@ public void setUp() throws IOException { void shouldNotLogRequest() throws Exception { mvc.perform(get("/api/sync")); - verify(formatter, never()).format(any(Precorrelation.class)); - verify(writer, never()).writeRequest(any()); + verify(formatter, never()).format(any(), any(HttpRequest.class)); + verify(writer, never()).write(any(Precorrelation.class), any()); } @Test @@ -61,8 +62,8 @@ void shouldNotLogRequest() throws Exception { void shouldNotLogResponse() throws Exception { mvc.perform(get("/api/sync")); - verify(formatter, never()).format(any(Correlation.class)); - verify(writer, never()).writeRequest(any()); + verify(formatter, never()).format(any(), any(HttpResponse.class)); + verify(writer, never()).write(any(Correlation.class), any()); } } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java index ef3cb6bac..4efe6d959 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java @@ -6,14 +6,12 @@ import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; -import java.io.IOException; - import static org.hamcrest.Matchers.is; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; @@ -35,16 +33,15 @@ public final class TeeTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java index 882aedece..c0d848e1d 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java @@ -9,6 +9,7 @@ import org.springframework.test.web.servlet.setup.MockMvcBuilders; import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; @@ -16,7 +17,6 @@ import org.zalando.logbook.servlet.junit.RestoreSystemProperties; import javax.annotation.concurrent.NotThreadSafe; -import java.io.IOException; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.endsWith; @@ -42,16 +42,15 @@ public final class WritingTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() - .formatter(formatter) - .writer(writer) + .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() throws IOException { + public void setUp() { reset(formatter, writer); - when(writer.isActive(any())).thenReturn(true); + when(writer.isActive()).thenReturn(true); } @Test @@ -84,13 +83,12 @@ private void shouldLogRequestBody(final String contentType, final String content .contentType(contentType) .content(content)); - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(writer).writeRequest(captor.capture()); - final Precorrelation precorrelation = captor.getValue(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String request = captor.getValue(); - assertThat(precorrelation.getRequest(), startsWith("Incoming Request:")); - assertThat(precorrelation.getRequest(), endsWith( + assertThat(request, startsWith("Incoming Request:")); + assertThat(request, endsWith( "GET http://localhost/api/sync HTTP/1.1\n" + "Accept: application/json\n" + "Host: localhost\n" + @@ -110,13 +108,12 @@ void shouldNotLogFormRequestOff() throws Exception { .contentType(MediaType.APPLICATION_FORM_URLENCODED) .content("hello=world")); - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Precorrelation.class); - verify(writer).writeRequest(captor.capture()); - final Precorrelation precorrelation = captor.getValue(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String request = captor.getValue(); - assertThat(precorrelation.getRequest(), startsWith("Incoming Request:")); - assertThat(precorrelation.getRequest(), endsWith( + assertThat(request, startsWith("Incoming Request:")); + assertThat(request, endsWith( "GET http://localhost/api/sync HTTP/1.1\n" + "Accept: application/json\n" + "Host: localhost\n" + @@ -128,13 +125,12 @@ void shouldLogResponse() throws Exception { mvc.perform(get("/api/sync") .with(http11())); - @SuppressWarnings("unchecked") final ArgumentCaptor> captor = ArgumentCaptor.forClass( - Correlation.class); - verify(writer).writeResponse(captor.capture()); - final Correlation correlation = captor.getValue(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Correlation.class), captor.capture()); + final String response = captor.getValue(); - assertThat(correlation.getResponse(), startsWith("Outgoing Response:")); - assertThat(correlation.getResponse(), endsWith( + assertThat(response, startsWith("Outgoing Response:")); + assertThat(response, endsWith( "HTTP/1.1 200 OK\n" + "Content-Type: application/json;charset=UTF-8\n" + "\n" + diff --git a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java index 30e10b505..df621acd0 100644 --- a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java +++ b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java @@ -26,22 +26,19 @@ import org.zalando.logbook.CurlHttpLogFormatter; import org.zalando.logbook.DefaultHttpLogFormatter; import org.zalando.logbook.DefaultHttpLogWriter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HeaderFilter; import org.zalando.logbook.HeaderFilters; import org.zalando.logbook.HttpLogFormatter; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.JsonHttpLogFormatter; -import org.zalando.logbook.SplunkHttpLogFormatter; import org.zalando.logbook.Logbook; import org.zalando.logbook.QueryFilter; import org.zalando.logbook.QueryFilters; -import org.zalando.logbook.RawHttpRequest; -import org.zalando.logbook.RawRequestFilter; -import org.zalando.logbook.RawRequestFilters; -import org.zalando.logbook.RawResponseFilter; -import org.zalando.logbook.RawResponseFilters; import org.zalando.logbook.RequestFilter; import org.zalando.logbook.ResponseFilter; +import org.zalando.logbook.SplunkHttpLogFormatter; import org.zalando.logbook.httpclient.LogbookHttpRequestInterceptor; import org.zalando.logbook.httpclient.LogbookHttpResponseInterceptor; import org.zalando.logbook.servlet.LogbookFilter; @@ -51,7 +48,6 @@ import java.util.List; import java.util.function.Predicate; -import static java.util.stream.Collectors.toList; import static javax.servlet.DispatcherType.ASYNC; import static javax.servlet.DispatcherType.ERROR; import static javax.servlet.DispatcherType.REQUEST; @@ -82,41 +78,37 @@ public LogbookAutoConfiguration(final LogbookProperties properties) { @Bean @ConditionalOnMissingBean(Logbook.class) public Logbook logbook( - final Predicate condition, - final List rawRequestFilters, - final List rawResponseFilters, + final Predicate condition, final List headerFilters, final List queryFilters, final List bodyFilters, final List requestFilters, final List responseFilters, @SuppressWarnings("SpringJavaAutowiringInspection") final HttpLogFormatter formatter, - final HttpLogWriter writer - ) { + final HttpLogWriter writer) { + return Logbook.builder() .condition(mergeWithExcludes(mergeWithIncludes(condition))) - .rawRequestFilters(rawRequestFilters) - .rawResponseFilters(rawResponseFilters) .headerFilters(headerFilters) .queryFilters(queryFilters) .bodyFilters(bodyFilters) .requestFilters(requestFilters) .responseFilters(responseFilters) - .formatter(formatter) - .writer(writer) + // TODO strategy + .sink(new DefaultSink(formatter, writer)) .build(); } - private Predicate mergeWithExcludes(final Predicate predicate) { + private Predicate mergeWithExcludes(final Predicate predicate) { return properties.getExclude().stream() - .map(Conditions::requestTo) + .map(Conditions::requestTo) .map(Predicate::negate) .reduce(predicate, Predicate::and); } - private Predicate mergeWithIncludes(final Predicate predicate) { + private Predicate mergeWithIncludes(final Predicate predicate) { return properties.getInclude().stream() - .map(Conditions::requestTo) + .map(Conditions::requestTo) .reduce(Predicate::or) .map(predicate::and) .orElse(predicate); @@ -125,24 +117,10 @@ private Predicate mergeWithIncludes(final Predicate requestCondition() { + public Predicate requestCondition() { return $ -> true; } - @API(status = INTERNAL) - @Bean - @ConditionalOnMissingBean(RawRequestFilter.class) - public RawRequestFilter rawRequestFilter() { - return RawRequestFilters.defaultValue(); - } - - @API(status = INTERNAL) - @Bean - @ConditionalOnMissingBean(RawResponseFilter.class) - public RawResponseFilter rawResponseFilter() { - return RawResponseFilters.defaultValue(); - } - @API(status = INTERNAL) @Bean @ConditionalOnMissingBean(QueryFilter.class) @@ -152,7 +130,6 @@ public QueryFilter queryFilter() { QueryFilters.defaultValue() : parameters.stream() .map(parameter -> QueryFilters.replaceQuery(parameter, "XXX")) - .collect(toList()).stream() .reduce(QueryFilter::merge) .orElseGet(QueryFilter::none); } @@ -166,7 +143,6 @@ public HeaderFilter headerFilter() { HeaderFilters.defaultValue() : headers.stream() .map(header -> HeaderFilters.replaceHeaders(header::equalsIgnoreCase, "XXX")) - .collect(toList()).stream() .reduce(HeaderFilter::merge) .orElseGet(HeaderFilter::none); } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java index ce5841929..24a6cd641 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java @@ -7,18 +7,17 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.RawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; import java.util.function.Predicate; -import static java.util.Optional.empty; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.zalando.logbook.Conditions.exclude; import static org.zalando.logbook.Conditions.requestTo; @@ -28,7 +27,7 @@ class ExcludeTest { @TestConfiguration public static class Config { @Bean - public Predicate requestCondition() { + public Predicate requestCondition() { return exclude(requestTo("/health")); } } @@ -36,44 +35,53 @@ public Predicate requestCondition() { @Autowired private Logbook logbook; - @MockBean - private Logger httpLogger; + @MockBean(name = "httpLogger") + private Logger logger; @BeforeEach void setUp() { - doReturn(true).when(httpLogger).isTraceEnabled(); + doReturn(true).when(logger).isTraceEnabled(); } @Test void shouldExcludeHealth() throws IOException { - assertThat(logbook.write(request("/health")), is(empty())); + logbook.process(request("/health")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeAdmin() throws IOException { - assertThat(logbook.write(request("/admin")), is(empty())); + logbook.process(request("/admin")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeAdminWithPath() throws IOException { - assertThat(logbook.write(request("/admin/users")), is(empty())); + logbook.process(request("/admin/users")).write(); + + verify(logger, never()).trace(any()); } @Test - void shouldNotExcludeAdminWithQueryParameters() throws IOException { - assertThat(logbook.write(MockRawHttpRequest.create() + void shouldExcludeAdminWithQueryParameters() throws IOException { + logbook.process(MockHttpRequest.create() .withPath("/admin") - .withQuery("debug=true")), is(empty())); + .withQuery("debug=true")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldNotExcludeApi() throws IOException { - assertThat(logbook.write(request("/api")), is(not(empty()))); + logbook.process(request("/admin/api")).write(); + + verify(logger).trace(any()); } - private MockRawHttpRequest request(final String path) { - return MockRawHttpRequest.create() - .withPath(path); + private MockHttpRequest request(final String path) { + return MockHttpRequest.create().withPath(path); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java index a3fb3bfab..4cba22569 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java @@ -7,14 +7,12 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.Precorrelation; import java.io.IOException; -import java.util.function.Function; import static org.hamcrest.Matchers.containsString; -import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; @@ -31,19 +29,14 @@ class FormatStyleCurlTest { @BeforeEach void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + doReturn(true).when(writer).isActive(); } @Test void shouldUseHttpFormatter() throws IOException { - logbook.write(MockRawHttpRequest.create()); + logbook.process(MockHttpRequest.create()).write(); - verify(writer).writeRequest(argThat(isCurlFormatter())); - } - - private Matcher> isCurlFormatter() { - final Function, String> getRequest = Precorrelation::getRequest; - return hasFeature("request", getRequest, containsString("curl -v -X GET 'http://localhost/'")); + verify(writer).write(any(Precorrelation.class), argThat(containsString("curl -v -X GET 'http://localhost/'"))); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java index 2ae3663c3..3110a89be 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java @@ -7,16 +7,14 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.Precorrelation; import java.io.IOException; -import java.util.function.Function; import static org.hamcrest.Matchers.allOf; import static org.hamcrest.Matchers.endsWith; import static org.hamcrest.Matchers.startsWith; -import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; @@ -33,19 +31,14 @@ class FormatStyleDefaultTest { @BeforeEach void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + doReturn(true).when(writer).isActive(); } @Test void shouldUseJsonFormatter() throws IOException { - logbook.write(MockRawHttpRequest.create()); + logbook.process(MockHttpRequest.create()).write(); - verify(writer).writeRequest(argThat(isJsonFormatted())); - } - - private Matcher> isJsonFormatted() { - final Function, String> getRequest = Precorrelation::getRequest; - return hasFeature("request", getRequest, allOf(startsWith("{"), endsWith("}"))); + verify(writer).write(any(Precorrelation.class), argThat(allOf(startsWith("{"), endsWith("}")))); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java index d84989c13..b7a78cf97 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java @@ -7,14 +7,12 @@ import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.Precorrelation; import java.io.IOException; -import java.util.function.Function; import static org.hamcrest.Matchers.containsString; -import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; @@ -31,19 +29,14 @@ class FormatStyleHttpTest { @BeforeEach void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + doReturn(true).when(writer).isActive(); } @Test void shouldUseHttpFormatter() throws IOException { - logbook.write(MockRawHttpRequest.create()); + logbook.process(MockHttpRequest.create()).write(); - verify(writer).writeRequest(argThat(isHttpFormatted())); - } - - private Matcher> isHttpFormatted() { - final Function, String> getRequest = Precorrelation::getRequest; - return hasFeature("request", getRequest, containsString("GET http://localhost/ HTTP/1.1")); + verify(writer).write(any(Precorrelation.class), argThat(containsString("GET http://localhost/ HTTP/1.1"))); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java index 8d8835b59..00a21a38f 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java @@ -1,20 +1,16 @@ package org.zalando.logbook.spring; -import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.Precorrelation; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; -import java.util.function.Function; import static org.hamcrest.Matchers.stringContainsInOrder; -import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.verify; @@ -30,24 +26,19 @@ class FormatStyleSplunkTest { private HttpLogWriter writer; @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); } @Test void shouldUseSplunkFormatter() throws IOException { - logbook.write(MockRawHttpRequest.create()); + logbook.process(MockHttpRequest.create()).write(); - verify(writer).writeRequest(argThat(isSplunkFormatted())); - } - - private Matcher> isSplunkFormatted() { - final Function, String> getRequest = Precorrelation::getRequest; - return hasFeature("request", getRequest, stringContainsInOrder( + verify(writer).write(any(), argThat(stringContainsInOrder( "protocol=HTTP/1.1", "method=GET", "uri=http://localhost/" - )); + ))); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/IncludeTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/IncludeTest.java index 16426b96e..78344905b 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/IncludeTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/IncludeTest.java @@ -7,18 +7,17 @@ import org.springframework.boot.test.context.TestConfiguration; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.context.annotation.Bean; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.RawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; import java.util.function.Predicate; -import static java.util.Optional.empty; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.not; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; import static org.zalando.logbook.Conditions.exclude; import static org.zalando.logbook.Conditions.requestTo; @@ -28,7 +27,7 @@ class IncludeTest { @TestConfiguration public static class Config { @Bean - public Predicate condition() { + public Predicate condition() { return exclude(requestTo("/api/admin/**")); } } @@ -37,60 +36,78 @@ public Predicate condition() { private Logbook logbook; @MockBean - private Logger httpLogger; + private Logger logger; @BeforeEach void setUp() { - doReturn(true).when(httpLogger).isTraceEnabled(); + doReturn(true).when(logger).isTraceEnabled(); } @Test void shouldExcludeAdmin() throws IOException { - assertThat(logbook.write(request("/api/admin")), is(empty())); + logbook.process(request("/api/admin")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeAdminWithPath() throws IOException { - assertThat(logbook.write(request("/api/admin/users")), is(empty())); + logbook.process(request("/api/admin/users")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeAdminWithQueryParameters() throws IOException { - assertThat(logbook.write(request("/api/admin").withQuery("debug=true")), is(empty())); + logbook.process(request("/api/admin").withQuery("debug=true")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeInternalApi() throws IOException { - assertThat(logbook.write(request("/internal-api")), is(empty())); + logbook.process(request("/internal-api")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeInternalApiWithPath() throws IOException { - assertThat(logbook.write(request("/internal-api/users")), is(empty())); + logbook.process(request("/internal-api/users")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldExcludeInternalApiWithQueryParameters() throws IOException { - assertThat(logbook.write(request("/internal-api").withQuery("debug=true")), is(empty())); + logbook.process(request("/internal-api").withQuery("debug=true")).write(); + + verify(logger, never()).trace(any()); } @Test void shouldNotExcludeApi() throws IOException { - assertThat(logbook.write(request("/api")), is(not(empty()))); + logbook.process(request("/api")).write(); + + verify(logger).trace(any()); } @Test void shouldNotExcludeApiWithPath() throws IOException { - assertThat(logbook.write(request("/api/users")), is(not(empty()))); + logbook.process(request("/api/users")).write(); + + verify(logger).trace(any()); } @Test void shouldNotExcludeApiWithParameter() throws IOException { - assertThat(logbook.write(request("/api").withQuery("debug=true")), is(not(empty()))); + logbook.process(request("/api").withQuery("debug=true")).write(); + + verify(logger).trace(any()); } - private MockRawHttpRequest request(final String path) { - return MockRawHttpRequest.create().withPath(path); + private MockHttpRequest request(final String path) { + return MockHttpRequest.create().withPath(path); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java index 635bf8765..9bcf681cd 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java @@ -3,15 +3,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.BodyFilter; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.Precorrelation; -import org.zalando.logbook.RawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; @@ -35,24 +33,22 @@ class ObfuscateBodyCustomTest { @MockBean private BodyFilter bodyFilter; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); doReturn("").when(bodyFilter).filter(anyString(), anyString()); } @Test void shouldFilterRequestBody() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create() + final HttpRequest request = MockHttpRequest.create() .withBodyAsString("Hello"); - logbook.write(request); + logbook.process(request).write(); - verify(writer).writeRequest(captor.capture()); - final String message = captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(), captor.capture()); + final String message = captor.getValue(); assertThat(message, not(containsString("Hello"))); assertThat(message, containsString("")); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java index 0695e863b..975cf8853 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java @@ -3,15 +3,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; import org.zalando.logbook.MockHeaders; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.Precorrelation; -import org.zalando.logbook.RawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; @@ -30,27 +28,25 @@ class ObfuscateHeadersCustomTest { @MockBean private HttpLogWriter writer; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); } @Test void shouldFilterHeaders() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create() + final HttpRequest request = MockHttpRequest.create() .withHeaders(MockHeaders.of( "Authorization", "123", "X-Access-Token", "123", "X-Trace-ID", "ABC" )); - logbook.write(request); + logbook.process(request).write(); - verify(writer).writeRequest(captor.capture()); - final String message = captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(), captor.capture()); + final String message = captor.getValue(); assertThat(message, containsString("Authorization: XXX")); assertThat(message, containsString("X-Access-Token: XXX")); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersDefaultTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersDefaultTest.java index 4f84fe605..a805f1e8d 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersDefaultTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersDefaultTest.java @@ -3,15 +3,14 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; import org.zalando.logbook.MockHeaders; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.Precorrelation; -import org.zalando.logbook.RawHttpRequest; import java.io.IOException; @@ -30,26 +29,24 @@ class ObfuscateHeadersDefaultTest { @MockBean private HttpLogWriter writer; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); } @Test void shouldFilterAuthorizationByDefault() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create() + final HttpRequest request = MockHttpRequest.create() .withHeaders(MockHeaders.of( "Authorization", "123", "X-Secret", "123" )); - logbook.write(request); + logbook.process(request).write(); - verify(writer).writeRequest(captor.capture()); - final String message = captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String message = captor.getValue(); assertThat(message, containsString("Authorization: XXX")); assertThat(message, containsString("X-Secret: 123")); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersCustomTest.java index 8407c05cc..556697042 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersCustomTest.java @@ -3,14 +3,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.Precorrelation; -import org.zalando.logbook.RawHttpRequest; import java.io.IOException; @@ -29,23 +28,21 @@ class ObfuscateParametersCustomTest { @MockBean private HttpLogWriter writer; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); } @Test void shouldFilterParameters() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create() + final HttpRequest request = MockHttpRequest.create() .withQuery("access_token=s3cr3t&q=logbook"); - logbook.write(request); + logbook.process(request).write(); - verify(writer).writeRequest(captor.capture()); - final String message = captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String message = captor.getValue(); assertThat(message, containsString("access_token=s3cr3t&q=XXX")); } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersDefaultTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersDefaultTest.java index 906eed444..f35a1f6b7 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersDefaultTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateParametersDefaultTest.java @@ -3,14 +3,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.Precorrelation; -import org.zalando.logbook.RawHttpRequest; import java.io.IOException; @@ -29,23 +28,21 @@ class ObfuscateParametersDefaultTest { @MockBean private HttpLogWriter writer; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); } @Test void shouldFilterAccessTokenByDefault() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create() + final HttpRequest request = MockHttpRequest.create() .withQuery("access_token=123&name=Alice&limit=1"); - logbook.write(request); + logbook.process(request).write(); - verify(writer).writeRequest(captor.capture()); - final String message = captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String message = captor.getValue(); assertThat(message, containsString("access_token=XXX&name=Alice&limit=1")); } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateRequestCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateRequestCustomTest.java index 765259ffa..be8c68b76 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateRequestCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateRequestCustomTest.java @@ -3,15 +3,13 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; import org.zalando.logbook.MockHttpRequest; -import org.zalando.logbook.MockRawHttpRequest; import org.zalando.logbook.Precorrelation; -import org.zalando.logbook.RawHttpRequest; import org.zalando.logbook.RequestFilter; import java.io.IOException; @@ -35,24 +33,22 @@ class ObfuscateRequestCustomTest { @MockBean private RequestFilter requestFilter; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); doReturn(MockHttpRequest.create().withBodyAsString("")).when(requestFilter).filter(any()); } @Test void shouldFilterRequestBody() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create() + final HttpRequest request = MockHttpRequest.create() .withBodyAsString("Hello"); - logbook.write(request); + logbook.process(request).write(); - verify(writer).writeRequest(captor.capture()); - final String message = captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + final String message = captor.getValue(); assertThat(message, not(containsString("Hello"))); assertThat(message, containsString("")); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateResponseCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateResponseCustomTest.java index 0ba4f32cb..3acfeb362 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateResponseCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateResponseCustomTest.java @@ -3,20 +3,16 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.Correlation; -import org.zalando.logbook.Correlator; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; +import org.zalando.logbook.MockHttpRequest; import org.zalando.logbook.MockHttpResponse; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.MockRawHttpResponse; import org.zalando.logbook.ResponseFilter; import java.io.IOException; -import java.util.Optional; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; @@ -37,24 +33,21 @@ class ObfuscateResponseCustomTest { @MockBean private ResponseFilter responseFilter; - @Captor - private ArgumentCaptor> captor; - @BeforeEach - void setUp() throws IOException { - doReturn(true).when(writer).isActive(any()); + void setUp() { + doReturn(true).when(writer).isActive(); doReturn(MockHttpResponse.create().withBodyAsString("")).when(responseFilter).filter(any()); } @Test void shouldFilterResponseBody() throws IOException { - final Optional correlator = logbook.write(MockRawHttpRequest.create()); - - correlator.get().write(MockRawHttpResponse.create() - .withBodyAsString("Hello")); + logbook.process(MockHttpRequest.create()).write() + .process(MockHttpResponse.create() + .withBodyAsString("Hello")).write(); - verify(writer).writeResponse(captor.capture()); - final String message = captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Correlation.class), captor.capture()); + final String message = captor.getValue(); assertThat(message, not(containsString("Hello"))); assertThat(message, containsString("")); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteCustomTest.java index b36b91936..226faf6e7 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteCustomTest.java @@ -4,9 +4,9 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; -import org.zalando.logbook.RawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; @@ -23,11 +23,11 @@ class WriteCustomTest { @Test void shouldUseCustomWriter() throws IOException { - final RawHttpRequest request = MockRawHttpRequest.create(); + final HttpRequest request = MockHttpRequest.create(); - logbook.write(request); + logbook.process(request).write(); - verify(writer).isActive(request); + verify(writer).isActive(); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java index c14e13561..76616eca6 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java @@ -6,7 +6,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.mock.mockito.MockBean; import org.zalando.logbook.Logbook; -import org.zalando.logbook.MockRawHttpRequest; +import org.zalando.logbook.MockHttpRequest; import java.io.IOException; @@ -29,8 +29,8 @@ void setUp() { } @Test - void shouldUseConfiguredLevel() throws IOException { - logbook.write(MockRawHttpRequest.create()); + public void shouldUseConfiguredLevel() throws IOException { + logbook.process(MockHttpRequest.create()).write(); verify(logger).warn(anyString()); } diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java b/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java index 4d885a2ab..63eb6596b 100644 --- a/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java +++ b/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java @@ -28,7 +28,7 @@ public static Map> of(final String k1, final String v1, fin } private static Map> buildHeaders(final String... x) { - final BaseHttpMessage.HeadersBuilder builder = new BaseHttpMessage.HeadersBuilder(); + final HttpMessage.HeadersBuilder builder = new HttpMessage.HeadersBuilder(); for (int i = 0; i < x.length; i += 2) { builder.put(x[i], x[i + 1]); } diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java b/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java index 864de1af3..3cf0e53e4 100644 --- a/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java +++ b/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java @@ -45,4 +45,14 @@ public byte[] getBody() { return bodyAsString.getBytes(UTF_8); } + @Override + public HttpRequest withBody() { + return this; + } + + @Override + public HttpRequest withoutBody() { + return withBodyAsString(""); + } + } diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java b/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java index 852eb70f5..dd208baef 100644 --- a/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java +++ b/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java @@ -38,4 +38,14 @@ public byte[] getBody() { return bodyAsString.getBytes(UTF_8); } + @Override + public HttpResponse withBody() { + return this; + } + + @Override + public HttpResponse withoutBody() { + return withBodyAsString(""); + } + } diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockRawHttpRequest.java b/logbook-test/src/main/java/org/zalando/logbook/MockRawHttpRequest.java deleted file mode 100644 index e2f60e3bf..000000000 --- a/logbook-test/src/main/java/org/zalando/logbook/MockRawHttpRequest.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.zalando.logbook; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.FieldDefaults; -import lombok.experimental.Wither; -import org.apiguardian.api.API; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Optional; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.emptyMap; -import static lombok.AccessLevel.PRIVATE; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.zalando.logbook.Origin.REMOTE; - -@API(status = MAINTAINED) -@FieldDefaults(level = PRIVATE) -@Getter -@Wither -@NoArgsConstructor(staticName = "create") -@AllArgsConstructor -public final class MockRawHttpRequest implements RawHttpRequest { - - String protocolVersion = "HTTP/1.1"; - Origin origin = REMOTE; - String remote = "127.0.0.1"; - String method = "GET"; - String scheme = "http"; - String host = "localhost"; - Optional port = Optional.of(80); - String path = "/"; - String query = ""; - Map> headers = emptyMap(); - String contentType = "text/plain"; - Charset charset = UTF_8; - String bodyAsString = ""; - - @Override - public MockHttpRequest withBody() throws IOException { - return MockHttpRequest.create() - .withProtocolVersion(protocolVersion) - .withOrigin(origin) - .withRemote(remote) - .withMethod(method) - .withScheme(scheme) - .withHost(host) - .withPort(port) - .withPath(path) - .withQuery(query) - .withHeaders(headers) - .withContentType(contentType) - .withCharset(charset) - .withBodyAsString(bodyAsString); - } - -} diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockRawHttpResponse.java b/logbook-test/src/main/java/org/zalando/logbook/MockRawHttpResponse.java deleted file mode 100644 index ad3dbdef1..000000000 --- a/logbook-test/src/main/java/org/zalando/logbook/MockRawHttpResponse.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.zalando.logbook; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.experimental.FieldDefaults; -import lombok.experimental.Wither; -import org.apiguardian.api.API; - -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static java.util.Collections.emptyMap; -import static lombok.AccessLevel.PRIVATE; -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.zalando.logbook.Origin.LOCAL; - -@API(status = MAINTAINED) -@FieldDefaults(level = PRIVATE) -@Getter -@Wither -@NoArgsConstructor(staticName = "create") -@AllArgsConstructor -public final class MockRawHttpResponse implements RawHttpResponse { - - String protocolVersion = "HTTP/1.1"; - Origin origin = LOCAL; - int status = 200; - Map> headers = emptyMap(); - String contentType = "text/plain"; - Charset charset = UTF_8; - String bodyAsString = ""; - - @Override - public MockHttpResponse withBody() throws IOException { - return MockHttpResponse.create() - .withProtocolVersion(protocolVersion) - .withOrigin(origin) - .withStatus(status) - .withHeaders(headers) - .withContentType(contentType) - .withCharset(charset) - .withBodyAsString(bodyAsString); - } - -} diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java b/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java index 690f813a0..ad19c722b 100644 --- a/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java +++ b/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java @@ -17,7 +17,7 @@ public interface MockHttpMessageTester { - default void verifyRequest(final BaseHttpRequest unit) throws IOException { + default void verifyRequest(final HttpRequest unit) throws IOException { assertThat(unit.getProtocolVersion(), is("HTTP/1.1")); assertThat(unit.getOrigin(), is(REMOTE)); assertThat(unit.getRemote(), is("127.0.0.1")); @@ -34,7 +34,7 @@ default void verifyRequest(final BaseHttpRequest unit) throws IOException { assertThat(unit.getCharset(), is(UTF_8)); } - default void verifyResponse(final BaseHttpResponse unit) throws IOException { + default void verifyResponse(final HttpResponse unit) throws IOException { assertThat(unit.getProtocolVersion(), is("HTTP/1.1")); assertThat(unit.getOrigin(), is(LOCAL)); assertThat(unit.getStatus(), is(200)); diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockRawHttpRequestTest.java b/logbook-test/src/test/java/org/zalando/logbook/MockRawHttpRequestTest.java deleted file mode 100644 index 78a580a5a..000000000 --- a/logbook-test/src/test/java/org/zalando/logbook/MockRawHttpRequestTest.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; -import java.util.Optional; - -import static java.nio.charset.StandardCharsets.ISO_8859_1; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.zalando.fauxpas.FauxPas.throwingFunction; -import static org.zalando.logbook.MockHeaders.of; -import static org.zalando.logbook.Origin.LOCAL; - -public final class MockRawHttpRequestTest implements MockHttpMessageTester { - - private final MockRawHttpRequest unit = MockRawHttpRequest.create(); - - @Test - void shouldDelegate() throws IOException { - verifyRequest(unit); - } - - @Test - void shouldDelegateWithBody() throws IOException { - final HttpRequest request = unit.withBody(); - verifyRequest(request); - assertThat(request.getBody(), is("".getBytes(UTF_8))); - assertThat(request.getBodyAsString(), is("")); - } - - @Test - void shouldUseNonDefaultPort() { - final MockRawHttpRequest unit = MockRawHttpRequest.create().withPort(Optional.of(8080)); - - assertThat(unit.getPort(), is(Optional.of(8080))); - } - - @Test - void shouldOptimizeWith() { - assertWith(unit, MockRawHttpRequest::withProtocolVersion, "HTTP/2", RawHttpRequest::getProtocolVersion); - assertWith(unit, MockRawHttpRequest::withOrigin, LOCAL, RawHttpRequest::getOrigin); - assertWith(unit, MockRawHttpRequest::withRemote, "192.168.0.1", RawHttpRequest::getRemote); - assertWith(unit, MockRawHttpRequest::withMethod, "POST", RawHttpRequest::getMethod); - assertWith(unit, MockRawHttpRequest::withScheme, "https", RawHttpRequest::getScheme); - assertWith(unit, MockRawHttpRequest::withHost, "example.org", RawHttpRequest::getHost); - assertWith(unit, MockRawHttpRequest::withPort, Optional.of(443), RawHttpRequest::getPort); - assertWith(unit, MockRawHttpRequest::withPath, "/index.html", RawHttpRequest::getPath); - assertWith(unit, MockRawHttpRequest::withQuery, "?", RawHttpRequest::getQuery); - assertWith(unit, MockRawHttpRequest::withHeaders, of("Accept", "text/plain"), RawHttpRequest::getHeaders); - assertWith(unit, MockRawHttpRequest::withContentType, "text/xml", RawHttpRequest::getContentType); - assertWith(unit, MockRawHttpRequest::withCharset, ISO_8859_1, RawHttpRequest::getCharset); - assertWith(unit, MockRawHttpRequest::withBodyAsString, "Hello", - throwingFunction(MockRawHttpRequest::getBodyAsString)); - } - -} diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockRawHttpResponseTest.java b/logbook-test/src/test/java/org/zalando/logbook/MockRawHttpResponseTest.java deleted file mode 100644 index a4b08a084..000000000 --- a/logbook-test/src/test/java/org/zalando/logbook/MockRawHttpResponseTest.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.zalando.logbook; - -import org.junit.jupiter.api.Test; - -import java.io.IOException; - -import static java.nio.charset.StandardCharsets.ISO_8859_1; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.zalando.fauxpas.FauxPas.throwingFunction; -import static org.zalando.logbook.MockHeaders.of; -import static org.zalando.logbook.Origin.REMOTE; - -public final class MockRawHttpResponseTest implements MockHttpMessageTester { - - private final MockRawHttpResponse unit = MockRawHttpResponse.create(); - - @Test - void shouldDelegate() throws IOException { - verifyResponse(unit); - } - - @Test - void shouldDelegateWithBody() throws IOException { - final HttpResponse response = unit.withBody(); - verifyResponse(response); - assertThat(response.getBody(), is("".getBytes(UTF_8))); - assertThat(response.getBodyAsString(), is("")); - } - - @Test - void shouldUseNonDefaultStatusCode() { - final MockRawHttpResponse unit = MockRawHttpResponse.create().withStatus(201); - - assertThat(unit.getStatus(), is(201)); - } - - @Test - void shouldSupportWith() { - assertWith(unit, MockRawHttpResponse::withProtocolVersion, "HTTP/2", RawHttpResponse::getProtocolVersion); - assertWith(unit, MockRawHttpResponse::withOrigin, REMOTE, RawHttpResponse::getOrigin); - assertWith(unit, MockRawHttpResponse::withStatus, 404, RawHttpResponse::getStatus); - assertWith(unit, MockRawHttpResponse::withHeaders, of("Accept", "text/plain"), RawHttpResponse::getHeaders); - assertWith(unit, MockRawHttpResponse::withContentType, "text/xml", RawHttpResponse::getContentType); - assertWith(unit, MockRawHttpResponse::withCharset, ISO_8859_1, RawHttpResponse::getCharset); - assertWith(unit, MockRawHttpResponse::withBodyAsString, "Hello", - throwingFunction(MockRawHttpResponse::getBodyAsString)); - } - -} From 7832845ea602e9f9fcb05d4a5ffeb13fb30aabfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Fri, 22 Feb 2019 00:13:52 +0100 Subject: [PATCH 04/13] Made tests pass --- logbook-api/pom.xml | 2 +- .../java/org/zalando/logbook/Correlation.java | 5 -- .../main/java/org/zalando/logbook/Sink.java | 1 - logbook-bom/pom.xml | 20 ++++---- logbook-core/pom.xml | 2 +- .../org/zalando/logbook/DefaultLogbook.java | 5 ++ logbook-httpclient/pom.xml | 2 +- logbook-jaxrs/pom.xml | 2 +- .../logbook/jaxrs/LogbookClientFilter.java | 4 +- logbook-okhttp/pom.xml | 2 +- .../zalando/logbook/okhttp/package-info.java | 3 ++ .../logbook/okhttp/GzipInterceptorTest.java | 28 ++++++----- logbook-okhttp2/pom.xml | 2 +- .../logbook/okhttp2/GzipInterceptor.java | 21 ++++---- .../zalando/logbook/okhttp2/LocalRequest.java | 47 +++++++++--------- .../logbook/okhttp2/LogbookInterceptor.java | 15 ++---- .../logbook/okhttp2/RemoteResponse.java | 48 +++++++++---------- .../logbook/okhttp2/GzipInterceptorTest.java | 21 ++++---- .../okhttp2/LogbookInterceptorTest.java | 38 +++++++-------- logbook-servlet/pom.xml | 2 +- .../logbook/servlet/SecurityStrategy.java | 8 ++-- logbook-spring-boot-starter/pom.xml | 2 +- .../zalando/logbook/spring/ExcludeTest.java | 7 --- .../logbook/spring/FormatStyleSplunkTest.java | 3 +- .../spring/ObfuscateBodyCustomTest.java | 3 +- .../spring/ObfuscateHeadersCustomTest.java | 3 +- logbook-test/pom.xml | 2 +- pom.xml | 2 +- 28 files changed, 147 insertions(+), 153 deletions(-) diff --git a/logbook-api/pom.xml b/logbook-api/pom.xml index 682d5cf25..3f840004b 100644 --- a/logbook-api/pom.xml +++ b/logbook-api/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-api Logbook: API diff --git a/logbook-api/src/main/java/org/zalando/logbook/Correlation.java b/logbook-api/src/main/java/org/zalando/logbook/Correlation.java index 5600e1c61..02ca187cc 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Correlation.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Correlation.java @@ -13,9 +13,4 @@ public interface Correlation extends Precorrelation { Duration getDuration(); - @Override - default Correlation correlate() { - return this; - } - } diff --git a/logbook-api/src/main/java/org/zalando/logbook/Sink.java b/logbook-api/src/main/java/org/zalando/logbook/Sink.java index 3c6c8028e..819c4d6ff 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Sink.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Sink.java @@ -11,7 +11,6 @@ default boolean isActive() { void write(Precorrelation precorrelation, HttpRequest request) throws IOException; void write(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException; - // TODO needed?! void writeBoth(Correlation correlation, HttpRequest request, HttpResponse response) throws IOException; } diff --git a/logbook-bom/pom.xml b/logbook-bom/pom.xml index 8c6491707..dc399ce7a 100644 --- a/logbook-bom/pom.xml +++ b/logbook-bom/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-bom pom @@ -20,47 +20,47 @@ org.zalando logbook-api - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-core - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-httpclient - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-jaxrs - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-okhttp - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-okhttp2 - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-servlet - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-spring-boot-starter - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT org.zalando logbook-test - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT test diff --git a/logbook-core/pom.xml b/logbook-core/pom.xml index 02f0f72d2..71e2f9864 100644 --- a/logbook-core/pom.xml +++ b/logbook-core/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-core Logbook: Core diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java index 5f5f26dd2..2df330f52 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java @@ -96,6 +96,11 @@ public Duration getDuration() { return duration; } + @Override + public Correlation correlate() { + return this; + } + } } diff --git a/logbook-httpclient/pom.xml b/logbook-httpclient/pom.xml index 451a8c3ca..76f9b5c60 100644 --- a/logbook-httpclient/pom.xml +++ b/logbook-httpclient/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-httpclient Logbook: HTTP Client diff --git a/logbook-jaxrs/pom.xml b/logbook-jaxrs/pom.xml index d80283ef8..3e95d83e5 100644 --- a/logbook-jaxrs/pom.xml +++ b/logbook-jaxrs/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-jaxrs Logbook: JAX-RS diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java index 7abc41bf0..1ba84a7c3 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookClientFilter.java @@ -53,8 +53,8 @@ public void aroundWriteTo(final WriterInterceptorContext context) throws IOExcep @Override public void filter(final ClientRequestContext request, final ClientResponseContext response) { read(request::getProperty, "process-response", ResponseProcessingStage.class) - .ifPresent(throwingConsumer(correlator -> - correlator.process(new RemoteResponse(response)).write())); + .ifPresent(throwingConsumer(stage -> + stage.process(new RemoteResponse(response)).write())); } private static Optional read(final Function provider, final String name, diff --git a/logbook-okhttp/pom.xml b/logbook-okhttp/pom.xml index 3c3efa946..981723e85 100644 --- a/logbook-okhttp/pom.xml +++ b/logbook-okhttp/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-okhttp Logbook: OkHttp diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/package-info.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/package-info.java index e8741b7ff..394c371dc 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/package-info.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/package-info.java @@ -1,4 +1,7 @@ +@EverythingIsNonNull @ParametersAreNonnullByDefault package org.zalando.logbook.okhttp; +import okhttp3.internal.annotations.EverythingIsNonNull; + import javax.annotation.ParametersAreNonnullByDefault; diff --git a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/GzipInterceptorTest.java b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/GzipInterceptorTest.java index 5946901ed..492bcae54 100644 --- a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/GzipInterceptorTest.java +++ b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/GzipInterceptorTest.java @@ -8,26 +8,33 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.zalando.logbook.Correlation; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; import java.io.IOException; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.GET; -import static com.github.restdriver.clientdriver.RestClientDriver.*; +import static com.github.restdriver.clientdriver.RestClientDriver.giveResponse; +import static com.github.restdriver.clientdriver.RestClientDriver.giveResponseAsBytes; +import static com.github.restdriver.clientdriver.RestClientDriver.onRequestTo; import static com.google.common.io.Resources.getResource; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.*; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.containsStringIgnoringCase; +import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; final class GzipInterceptorTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() - .writer(writer) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); private final OkHttpClient client = new OkHttpClient.Builder() @@ -38,8 +45,8 @@ final class GzipInterceptorTest { private final ClientDriver driver = new ClientDriverFactory().createClientDriver(); @BeforeEach - void defaultBehaviour() throws IOException { - when(writer.isActive(any())).thenReturn(true); + void defaultBehaviour() { + when(writer.isActive()).thenReturn(true); } @Test @@ -74,11 +81,10 @@ private void execute() throws IOException { assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureResponse() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Correlation.class); - verify(writer).writeResponse(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(), captor.capture()); + return captor.getValue(); } } diff --git a/logbook-okhttp2/pom.xml b/logbook-okhttp2/pom.xml index bf10bce5b..fa4af4361 100644 --- a/logbook-okhttp2/pom.xml +++ b/logbook-okhttp2/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-okhttp2 Logbook: OkHttp2 diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java index a37914cbf..2a0d3dc54 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java @@ -1,20 +1,18 @@ package org.zalando.logbook.okhttp2; -import java.io.IOException; - import com.squareup.okhttp.Headers; import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.Response; import com.squareup.okhttp.internal.http.RealResponseBody; - +import okio.GzipSource; import org.apiguardian.api.API; +import java.io.IOException; + import static java.util.Objects.requireNonNull; import static okio.Okio.buffer; import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import okio.GzipSource; - @API(status = EXPERIMENTAL) public final class GzipInterceptor implements Interceptor { @@ -23,15 +21,16 @@ public Response intercept(final Chain chain) throws IOException { final Response response = chain.proceed(chain.request()); if (isContentEncodingGzip(response)) { - Headers headers = response.headers() - .newBuilder() - .removeAll("Content-Encoding") - .removeAll("Content-Length") - .build(); + final Headers headers = response.headers() + .newBuilder() + .removeAll("Content-Encoding") + .removeAll("Content-Length") + .build(); + return response.newBuilder() .headers(headers) .body(new RealResponseBody( - headers.newBuilder().add("Content-Length", "-1").build(), + headers, buffer(new GzipSource(requireNonNull(response.body(), "body").source())))) .build(); } diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java index 4daacbf75..f96a6921e 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java @@ -1,27 +1,24 @@ package org.zalando.logbook.okhttp2; -import java.io.IOException; -import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import javax.annotation.Nullable; - import com.squareup.okhttp.MediaType; import com.squareup.okhttp.Request; import com.squareup.okhttp.RequestBody; - +import okio.Buffer; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpRequest; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.charset.Charset; +import java.util.List; +import java.util.Map; +import java.util.Optional; import static com.squareup.okhttp.HttpUrl.defaultPort; import static com.squareup.okhttp.RequestBody.create; import static java.nio.charset.StandardCharsets.UTF_8; -import okio.Buffer; - -final class LocalRequest implements RawHttpRequest, HttpRequest { +final class LocalRequest implements HttpRequest { private Request request; private byte[] body; @@ -100,23 +97,29 @@ private Optional contentType() { @Override public HttpRequest withBody() throws IOException { - @Nullable final RequestBody body = request.body(); - if (body == null) { - this.body = new byte[0]; - } else { - final byte[] bytes = bytes(body); + @Nullable final RequestBody entity = request.body(); - this.request = request.newBuilder() - .method(request.method(), create(body.contentType(), bytes)) - .build(); + if (entity == null) { + return withoutBody(); + } else { + this.body = bytes(entity); - this.body = bytes; + this.request = request.newBuilder() + .method(request.method(), create(entity.contentType(), body)) + .build(); + } } return this; } + @Override + public HttpRequest withoutBody() { + this.body = new byte[0]; + return this; + } + private static byte[] bytes(final RequestBody body) throws IOException { final Buffer buffer = new Buffer(); body.writeTo(buffer); @@ -129,7 +132,7 @@ Request toRequest() { @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LogbookInterceptor.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LogbookInterceptor.java index fc89b685b..0adb4ed2d 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LogbookInterceptor.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LogbookInterceptor.java @@ -1,17 +1,14 @@ package org.zalando.logbook.okhttp2; -import java.io.IOException; -import java.util.Optional; - import com.squareup.okhttp.Interceptor; import com.squareup.okhttp.Response; - import org.apiguardian.api.API; -import org.zalando.logbook.Correlator; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseProcessingStage; + +import java.io.IOException; import static org.apiguardian.api.API.Status.EXPERIMENTAL; -import static org.zalando.fauxpas.FauxPas.throwingConsumer; @API(status = EXPERIMENTAL) public final class LogbookInterceptor implements Interceptor { @@ -25,11 +22,9 @@ public LogbookInterceptor(final Logbook logbook) { @Override public Response intercept(final Chain chain) throws IOException { final LocalRequest request = new LocalRequest(chain.request()); - final Optional correlator = logbook.write(request); - + final ResponseProcessingStage stage = logbook.process(request).write(); final RemoteResponse response = new RemoteResponse(chain.proceed(request.toRequest())); - - correlator.ifPresent(throwingConsumer(c -> c.write(response))); + stage.process(response).write(); return response.toResponse(); } diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java index 713963094..6f5706680 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java @@ -1,5 +1,11 @@ package org.zalando.logbook.okhttp2; +import com.squareup.okhttp.MediaType; +import com.squareup.okhttp.Response; +import com.squareup.okhttp.ResponseBody; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Origin; + import java.io.IOException; import java.nio.charset.Charset; import java.util.List; @@ -7,21 +13,11 @@ import java.util.Map; import java.util.Optional; -import com.squareup.okhttp.MediaType; -import com.squareup.okhttp.Response; -import com.squareup.okhttp.ResponseBody; - -import org.zalando.logbook.HttpResponse; -import org.zalando.logbook.Origin; -import org.zalando.logbook.RawHttpResponse; - import static com.squareup.okhttp.ResponseBody.create; -import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED; -import static java.net.HttpURLConnection.HTTP_NO_CONTENT; import static java.nio.charset.StandardCharsets.UTF_8; import static java.util.Objects.requireNonNull; -final class RemoteResponse implements RawHttpResponse, HttpResponse { +final class RemoteResponse implements HttpResponse { private Response response; private byte[] body; @@ -68,28 +64,28 @@ private Optional contentType() { @Override public HttpResponse withBody() throws IOException { - final ResponseBody body = requireNonNull(response.body(), "Body is never null for normal responses"); + if (body == null) { + final ResponseBody entity = requireNonNull(response.body(), "Body is never null for normal responses"); - if (canSkipBody(response)) { - this.body = new byte[0]; - } else { - final byte[] bytes = body.bytes(); + if (entity.contentLength() == 0L) { + return withoutBody(); + } else { + this.body = entity.bytes(); - this.response = response.newBuilder() - .body(create(body.contentType(), bytes)) - .build(); + this.response = response.newBuilder() + .body(create(entity.contentType(), body)) + .build(); - this.body = bytes; + } } return this; } - static boolean canSkipBody(Response response) { - if("HEAD".equals(response.request().method())) { - return true; - } - return response.code() == HTTP_NO_CONTENT || response.code() == HTTP_NOT_MODIFIED; + @Override + public RemoteResponse withoutBody() { + this.body = new byte[0]; + return this; } public Response toResponse() { @@ -98,7 +94,7 @@ public Response toResponse() { @Override public byte[] getBody() { - return body; + return body == null ? new byte[0] : body; } } diff --git a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java index 4356070b6..21e58b5a8 100644 --- a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java +++ b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java @@ -1,20 +1,20 @@ package org.zalando.logbook.okhttp2; -import java.io.IOException; - import com.github.restdriver.clientdriver.ClientDriver; import com.github.restdriver.clientdriver.ClientDriverFactory; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.zalando.logbook.Correlation; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; +import java.io.IOException; + import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.GET; import static com.github.restdriver.clientdriver.RestClientDriver.giveResponse; import static com.github.restdriver.clientdriver.RestClientDriver.giveResponseAsBytes; @@ -34,7 +34,7 @@ final class GzipInterceptorTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() - .writer(writer) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); private final ClientDriver driver = new ClientDriverFactory().createClientDriver(); @@ -47,8 +47,8 @@ public GzipInterceptorTest() { } @BeforeEach - void defaultBehaviour() throws IOException { - when(writer.isActive(any())).thenReturn(true); + void defaultBehaviour() { + when(writer.isActive()).thenReturn(true); } @Test @@ -83,11 +83,10 @@ private void execute() throws IOException { assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureResponse() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Correlation.class); - verify(writer).writeResponse(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(), captor.capture()); + return captor.getValue(); } } diff --git a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java index 95e46e0d5..3673803e5 100644 --- a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java +++ b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java @@ -1,22 +1,22 @@ package org.zalando.logbook.okhttp2; -import java.io.IOException; -import java.net.HttpURLConnection; - import com.github.restdriver.clientdriver.ClientDriver; import com.github.restdriver.clientdriver.ClientDriverFactory; import com.squareup.okhttp.OkHttpClient; import com.squareup.okhttp.Request; import com.squareup.okhttp.Response; - import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; -import org.zalando.logbook.Correlation; +import org.zalando.logbook.DefaultHttpLogFormatter; +import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; +import java.io.IOException; +import java.net.HttpURLConnection; + import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.GET; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.HEAD; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.POST; @@ -42,7 +42,7 @@ final class LogbookInterceptorTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() - .writer(writer) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); private final OkHttpClient client; @@ -55,8 +55,8 @@ public LogbookInterceptorTest() { } @BeforeEach - void defaultBehaviour() throws IOException { - when(writer.isActive(any())).thenReturn(true); + void defaultBehaviour() { + when(writer.isActive()).thenReturn(true); } @Test @@ -91,22 +91,21 @@ void shouldLogRequestWithBody() throws IOException { assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureRequest() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Precorrelation.class); - verify(writer).writeRequest(captor.capture()); - return captor.getValue().getRequest(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(Precorrelation.class), captor.capture()); + return captor.getValue(); } @Test void shouldNotLogRequestIfInactive() throws IOException { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); sendAndReceive(); - verify(writer, never()).writeRequest(any()); + verify(writer, never()).write(any(Precorrelation.class), any()); } @Test @@ -174,22 +173,21 @@ void shouldLogResponseWithBody() throws IOException { assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureResponse() throws IOException { - final ArgumentCaptor> captor = ArgumentCaptor.forClass(Correlation.class); - verify(writer).writeResponse(captor.capture()); - return captor.getValue().getResponse(); + final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + verify(writer).write(any(), captor.capture()); + return captor.getValue(); } @Test void shouldNotLogResponseIfInactive() throws IOException { - when(writer.isActive(any())).thenReturn(false); + when(writer.isActive()).thenReturn(false); driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); sendAndReceive(); - verify(writer, never()).writeResponse(any()); + verify(writer, never()).write(any(), any()); } private void sendAndReceive() throws IOException { diff --git a/logbook-servlet/pom.xml b/logbook-servlet/pom.xml index 5d10580df..f9b747a18 100644 --- a/logbook-servlet/pom.xml +++ b/logbook-servlet/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-servlet Logbook: Servlet diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java index 76fe871c1..ad47e2401 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java @@ -31,15 +31,15 @@ public void doFilter(final Logbook logbook, final HttpServletRequest httpRequest chain.doFilter(request, response); if (isUnauthorizedOrForbidden(response)) { - final ResponseProcessingStage correlator; + final ResponseProcessingStage stage; if (isFirstRequest(request)) { - correlator = logbook.process(filter.filter(request)).write(); + stage = logbook.process(filter.filter(request)).write(); } else { - correlator = readCorrelator(request); + stage = readCorrelator(request); } - correlator.process(response).write(); + stage.process(response).write(); } } diff --git a/logbook-spring-boot-starter/pom.xml b/logbook-spring-boot-starter/pom.xml index b4705b465..b2a4ac864 100644 --- a/logbook-spring-boot-starter/pom.xml +++ b/logbook-spring-boot-starter/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-spring-boot-starter Logbook: Spring Boot Starter diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java index 24a6cd641..1e2114342 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ExcludeTest.java @@ -73,13 +73,6 @@ void shouldExcludeAdminWithQueryParameters() throws IOException { verify(logger, never()).trace(any()); } - @Test - void shouldNotExcludeApi() throws IOException { - logbook.process(request("/admin/api")).write(); - - verify(logger).trace(any()); - } - private MockHttpRequest request(final String path) { return MockHttpRequest.create().withPath(path); } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java index 00a21a38f..a089e133d 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleSplunkTest.java @@ -7,6 +7,7 @@ import org.zalando.logbook.HttpLogWriter; import org.zalando.logbook.Logbook; import org.zalando.logbook.MockHttpRequest; +import org.zalando.logbook.Precorrelation; import java.io.IOException; @@ -34,7 +35,7 @@ void setUp() { void shouldUseSplunkFormatter() throws IOException { logbook.process(MockHttpRequest.create()).write(); - verify(writer).write(any(), argThat(stringContainsInOrder( + verify(writer).write(any(Precorrelation.class), argThat(stringContainsInOrder( "protocol=HTTP/1.1", "method=GET", "uri=http://localhost/" diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java index 9bcf681cd..643ff00df 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateBodyCustomTest.java @@ -10,6 +10,7 @@ import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; import org.zalando.logbook.MockHttpRequest; +import org.zalando.logbook.Precorrelation; import java.io.IOException; @@ -47,7 +48,7 @@ void shouldFilterRequestBody() throws IOException { logbook.process(request).write(); final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(writer).write(any(), captor.capture()); + verify(writer).write(any(Precorrelation.class), captor.capture()); final String message = captor.getValue(); assertThat(message, not(containsString("Hello"))); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java index 975cf8853..95ff673d3 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/ObfuscateHeadersCustomTest.java @@ -10,6 +10,7 @@ import org.zalando.logbook.Logbook; import org.zalando.logbook.MockHeaders; import org.zalando.logbook.MockHttpRequest; +import org.zalando.logbook.Precorrelation; import java.io.IOException; @@ -45,7 +46,7 @@ void shouldFilterHeaders() throws IOException { logbook.process(request).write(); final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(writer).write(any(), captor.capture()); + verify(writer).write(any(Precorrelation.class), captor.capture()); final String message = captor.getValue(); assertThat(message, containsString("Authorization: XXX")); diff --git a/logbook-test/pom.xml b/logbook-test/pom.xml index e832cf13f..c525740b5 100644 --- a/logbook-test/pom.xml +++ b/logbook-test/pom.xml @@ -4,7 +4,7 @@ org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT logbook-test Logbook: Test diff --git a/pom.xml b/pom.xml index 61a855f14..2606eb9a7 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 org.zalando logbook-parent - 1.14.0-SNAPSHOT + 2.0.0-SNAPSHOT pom Logbook HTTP request and response logging From d2f65c18c2e0e57ef558393de02c51a02abe1453 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Fri, 22 Feb 2019 16:58:04 +0100 Subject: [PATCH 05/13] Fixed coverage --- .../java/org/zalando/logbook/Correlation.java | 4 + .../java/org/zalando/logbook/Headers.java | 31 ++ .../java/org/zalando/logbook/HttpMessage.java | 38 +- .../java/org/zalando/logbook/Strategy.java | 4 +- .../org/zalando/logbook/BodyFilterTest.java | 2 +- .../org/zalando/logbook/BodyReplacerTest.java | 2 +- .../org/zalando/logbook/CorrelationTest.java | 18 + .../zalando/logbook/EnforceCoverageTest.java | 2 +- .../org/zalando/logbook/ForwardingTest.java | 2 +- .../org/zalando/logbook/HeaderFilterTest.java | 2 +- .../java/org/zalando/logbook/HeadersTest.java | 62 ++++ .../zalando/logbook/HttpLogWriterTest.java | 2 +- .../org/zalando/logbook/HttpMessageTest.java | 2 +- .../zalando/logbook/LogbookFactoryTest.java | 2 +- .../java/org/zalando/logbook/LogbookTest.java | 2 +- .../org/zalando/logbook/QueryFilterTest.java | 2 +- .../zalando/logbook/RequestFilterTest.java | 2 +- .../org/zalando/logbook/RequestURITest.java | 17 +- .../zalando/logbook/ResponseFilterTest.java | 2 +- .../java/org/zalando/logbook/SinkTest.java | 18 + .../org/zalando/logbook/StrategyTest.java | 53 +++ .../java/org/zalando/logbook/Conditions.java | 5 +- .../org/zalando/logbook/DefaultLogbook.java | 5 - .../org/zalando/logbook/HeaderFilters.java | 34 +- .../org/zalando/logbook/MediaTypeQuery.java | 7 +- .../logbook/XmlCompactingBodyFilter.java | 2 +- .../logbook/BodyOnlyIfErrorStrategy.java | 10 +- .../zalando/logbook/BodyReplacersTest.java | 2 +- .../logbook/ChunkingHttpLogWriterTest.java | 2 +- .../logbook/ChunkingSpliteratorTest.java | 2 +- .../zalando/logbook/CompositeSinkTest.java | 73 ++++ .../org/zalando/logbook/ConditionsTest.java | 2 +- .../logbook/CurlHttpLogFormatterTest.java | 2 +- .../logbook/DefaultHttpLogFormatterTest.java | 2 +- .../DefaultHttpLogWriterLevelTest.java | 2 +- .../logbook/DefaultHttpLogWriterTest.java | 2 +- .../logbook/DefaultLogbookFactoryTest.java | 20 ++ .../zalando/logbook/DefaultLogbookTest.java | 7 +- .../org/zalando/logbook/DefaultSinkTest.java | 61 ++++ .../logbook/ErrorResponseOnlyStrategy.java | 10 +- .../logbook/FilteredHttpRequestTest.java | 2 +- .../logbook/FilteredHttpResponseTest.java | 2 +- .../zalando/logbook/HeaderFiltersTest.java | 2 +- .../logbook/JsonHttpLogFormatterTest.java | 2 +- .../zalando/logbook/MediaTypeQueryTest.java | 2 +- .../zalando/logbook/RequestFiltersTest.java | 18 +- .../zalando/logbook/RequestOnlyStrategy.java | 7 +- .../zalando/logbook/ResponseFiltersTest.java | 18 +- .../zalando/logbook/ResponseOnlyStrategy.java | 8 +- .../SomeRequestsWithoutBodyStrategy.java | 8 +- .../logbook/StreamHttpLogWriterTest.java | 2 +- .../logbook/httpclient/LocalRequest.java | 26 +- .../logbook/httpclient/RemoteResponse.java | 26 +- .../logbook/httpclient/AbstractHttpTest.java | 8 +- ...rwardingHttpAsyncResponseConsumerTest.java | 4 +- .../logbook/httpclient/LocalRequestTest.java | 24 +- .../httpclient/RemoteResponseTest.java | 36 +- .../zalando/logbook/jaxrs/RemoteRequest.java | 1 - .../logbook/jaxrs/ClientAndServerTest.java | 316 +++++++++++++++++ .../jaxrs/ClientAndServerWithoutBodyTest.java | 167 +++++++++ .../logbook/jaxrs/HttpMessagesTest.java | 6 +- .../jaxrs/JerseyClientAndServerTest.java | 325 ------------------ .../jaxrs/LogbookClientFilterTest.java | 4 +- .../jaxrs/LogbookServerFilterTest.java | 4 +- .../logbook/jaxrs/TeeOutputStreamTest.java | 6 +- .../logbook/jaxrs/UnitTestSetupException.java | 8 - .../logbook/jaxrs/WithBodyStrategy.java | 44 +++ .../logbook/jaxrs/WithoutBodyStrategy.java | 34 ++ .../jaxrs/testing/support/TestModel.java | 4 +- .../logbook/okhttp/GzipInterceptor.java | 2 +- .../zalando/logbook/okhttp/LocalRequest.java | 4 +- .../logbook/okhttp/RemoteResponse.java | 6 +- .../logbook/okhttp/LocalRequestTest.java | 2 +- .../okhttp/LogbookInterceptorTest.java | 57 ++- .../logbook/okhttp2/GzipInterceptor.java | 2 +- .../zalando/logbook/okhttp2/LocalRequest.java | 2 +- .../logbook/okhttp2/RemoteResponse.java | 4 +- .../logbook/okhttp2/GzipInterceptorTest.java | 2 +- .../logbook/okhttp2/LocalRequestTest.java | 2 +- .../okhttp2/LogbookInterceptorTest.java | 36 +- .../logbook/servlet/LocalResponse.java | 10 +- .../logbook/servlet/RemoteRequest.java | 10 +- .../logbook/servlet/AsyncDispatchTest.java | 18 +- .../logbook/servlet/ByteStreamsTest.java | 2 +- .../logbook/servlet/ErrorDispatchTest.java | 4 +- .../logbook/servlet/ExampleController.java | 2 +- .../logbook/servlet/FormattingTest.java | 4 +- .../servlet/ForwardingHttpLogFormatter.java | 6 +- .../logbook/servlet/HttpSupportTest.java | 2 +- .../logbook/servlet/LocalResponseTest.java | 4 +- .../logbook/servlet/LogbookFilterTest.java | 2 +- .../org/zalando/logbook/servlet/Message.java | 2 +- .../servlet/MultiFilterSecurityTest.java | 4 +- .../logbook/servlet/MultiFilterTest.java | 4 +- .../logbook/servlet/NullOutputStreamTest.java | 2 +- .../org/zalando/logbook/servlet/SkipTest.java | 4 +- .../org/zalando/logbook/servlet/TeeTest.java | 4 +- .../zalando/logbook/servlet/WritingTest.java | 4 +- .../junit/SystemPropertiesExtension.java | 2 +- .../zalando/logbook/spring/LogbookTest.java | 2 +- .../logbook/spring/WriteLevelTest.java | 2 +- .../java/org/zalando/logbook/MockHeaders.java | 15 +- .../org/zalando/logbook/MockHttpRequest.java | 3 +- .../org/zalando/logbook/MockHttpResponse.java | 3 +- .../org/zalando/logbook/MockHeadersTest.java | 2 +- .../logbook/MockHttpMessageTester.java | 2 +- .../zalando/logbook/MockHttpRequestTest.java | 14 +- .../zalando/logbook/MockHttpResponseTest.java | 14 +- 108 files changed, 1316 insertions(+), 583 deletions(-) create mode 100644 logbook-api/src/main/java/org/zalando/logbook/Headers.java create mode 100644 logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java create mode 100644 logbook-api/src/test/java/org/zalando/logbook/HeadersTest.java create mode 100644 logbook-api/src/test/java/org/zalando/logbook/SinkTest.java create mode 100644 logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java rename logbook-core/src/{main => test}/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java (78%) create mode 100644 logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java create mode 100644 logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java create mode 100644 logbook-core/src/test/java/org/zalando/logbook/DefaultSinkTest.java rename logbook-core/src/{main => test}/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java (75%) rename logbook-core/src/{main => test}/java/org/zalando/logbook/RequestOnlyStrategy.java (60%) rename logbook-core/src/{main => test}/java/org/zalando/logbook/ResponseOnlyStrategy.java (71%) rename logbook-core/src/{main => test}/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java (68%) create mode 100644 logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerTest.java create mode 100644 logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerWithoutBodyTest.java delete mode 100644 logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java delete mode 100644 logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/UnitTestSetupException.java create mode 100644 logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java create mode 100644 logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java diff --git a/logbook-api/src/main/java/org/zalando/logbook/Correlation.java b/logbook-api/src/main/java/org/zalando/logbook/Correlation.java index 02ca187cc..9ad39ba71 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Correlation.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Correlation.java @@ -13,4 +13,8 @@ public interface Correlation extends Precorrelation { Duration getDuration(); + @Override + default Correlation correlate() { + return this; + } } diff --git a/logbook-api/src/main/java/org/zalando/logbook/Headers.java b/logbook-api/src/main/java/org/zalando/logbook/Headers.java new file mode 100644 index 000000000..e5ad033e2 --- /dev/null +++ b/logbook-api/src/main/java/org/zalando/logbook/Headers.java @@ -0,0 +1,31 @@ +package org.zalando.logbook; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import static java.lang.String.CASE_INSENSITIVE_ORDER; +import static java.util.Collections.unmodifiableList; +import static java.util.Collections.unmodifiableMap; + +public final class Headers { + + private Headers() { + + } + + public static Map> empty() { + return new TreeMap<>(CASE_INSENSITIVE_ORDER); + } + + public static Map> immutableCopy(final Map> headers) { + final Map> copy = empty(); + + headers.forEach((header, values) -> + copy.put(header, unmodifiableList(new ArrayList<>(values)))); + + return unmodifiableMap(copy); + } + +} diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java b/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java index 303341bd3..e80856a2a 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java @@ -9,6 +9,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.TreeMap; import static java.lang.String.CASE_INSENSITIVE_ORDER; @@ -28,43 +29,6 @@ public interface HttpMessage { Charset getCharset(); - class HeadersBuilder { - - private Map> headers; - - public HeadersBuilder() { - // package private so we can trick code coverage - headers = new TreeMap<>(CASE_INSENSITIVE_ORDER); - } - - public HeadersBuilder put(final String key, final String value) { - final List values = headers.get(key); - if (values != null) { - values.add(value); - } else { - final ArrayList list = new ArrayList<>(); - list.add(value); - headers.put(key, list); - } - return this; - } - - public HeadersBuilder put(final String key, final Iterable values) { - for (final String value : values) { - put(key, value); - } - return this; - } - - public Map> build() { - for (final Map.Entry> e : headers.entrySet()) { - e.setValue(Collections.unmodifiableList(e.getValue())); - } - headers = Collections.unmodifiableMap(headers); - return headers; - } - } - byte[] getBody() throws IOException; default String getBodyAsString() throws IOException { diff --git a/logbook-api/src/main/java/org/zalando/logbook/Strategy.java b/logbook-api/src/main/java/org/zalando/logbook/Strategy.java index 755cd7ffc..163251426 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Strategy.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Strategy.java @@ -9,8 +9,6 @@ @API(status = STABLE) public interface Strategy { - // TODO default methods vs. DefaultStrategy - default HttpRequest process(final HttpRequest request) throws IOException { return request.withBody(); } @@ -20,7 +18,7 @@ default void write(final Precorrelation precorrelation, final HttpRequest reques sink.write(precorrelation, request); } - default HttpResponse process(HttpRequest request, final HttpResponse response) throws IOException { + default HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { return response.withBody(); } diff --git a/logbook-api/src/test/java/org/zalando/logbook/BodyFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/BodyFilterTest.java index 05a33d3c3..870807f7a 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/BodyFilterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/BodyFilterTest.java @@ -6,7 +6,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -public final class BodyFilterTest { +final class BodyFilterTest { @Test void noneShouldDefaultToNoOp() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/BodyReplacerTest.java b/logbook-api/src/test/java/org/zalando/logbook/BodyReplacerTest.java index b7b3db4a1..d55c46405 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/BodyReplacerTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/BodyReplacerTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.when; import static org.zalando.logbook.BodyReplacer.compound; -public final class BodyReplacerTest { +final class BodyReplacerTest { @Test void shouldStopOnFirstReplacerThatReplaced() throws IOException { diff --git a/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java b/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java new file mode 100644 index 000000000..4f50c7b69 --- /dev/null +++ b/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java @@ -0,0 +1,18 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; +import org.mockito.invocation.InvocationOnMock; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; + +class CorrelationTest { + + private final Correlation unit = mock(Correlation.class, InvocationOnMock::callRealMethod); + + @Test + void correlateNoOp() { + assertSame(unit, unit.correlate()); + } + +} diff --git a/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java b/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java index 73300275b..fe4115879 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/EnforceCoverageTest.java @@ -9,7 +9,7 @@ @Hack @OhNoYouDidnt -public final class EnforceCoverageTest { +final class EnforceCoverageTest { @Test void shouldCoverUselessClearMethods() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java b/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java index e65257434..3fd1fcf77 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/ForwardingTest.java @@ -9,7 +9,7 @@ import static org.mockito.Mockito.verify; -public final class ForwardingTest { +final class ForwardingTest { @Test void shouldForwardHttpRequests() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/HeaderFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/HeaderFilterTest.java index f0340bdc9..41e641bc3 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/HeaderFilterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/HeaderFilterTest.java @@ -11,7 +11,7 @@ import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.sameInstance; -public final class HeaderFilterTest { +final class HeaderFilterTest { @Test void noneShouldDefaultToNoOp() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/HeadersTest.java b/logbook-api/src/test/java/org/zalando/logbook/HeadersTest.java new file mode 100644 index 000000000..fcd07574f --- /dev/null +++ b/logbook-api/src/test/java/org/zalando/logbook/HeadersTest.java @@ -0,0 +1,62 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import static java.util.Collections.singletonList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class HeadersTest { + + @Test + void shouldMakeHeadersUnmodifiable() { + final Map> original = Headers.empty(); + + original.put("Authorization", new ArrayList<>(Arrays.asList( + "Basic dXNlcjpzZWNyZXQK", + "Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.e30."))); + + original.put("Content-Type", new ArrayList<>(singletonList("text/plain"))); + + final Map> unit = Headers.immutableCopy(original); + + assertThrows(UnsupportedOperationException.class, unit::clear); + assertThrows(UnsupportedOperationException.class, unit.get("Authorization")::clear); + } + + @Test + void shouldNotAllowNewHeaders() { + final Map> original = Headers.empty(); + original.put("Authorization", new ArrayList<>(singletonList("Basic dXNlcjpzZWNyZXQK"))); + + final Map> unit = Headers.immutableCopy(original); + + assertThat(unit, aMapWithSize(1)); + + original.put("Content-Type", new ArrayList<>(singletonList("text/plain"))); + + assertThat(unit, aMapWithSize(1)); + } + + @Test + void shouldNotAllowNewValues() { + final Map> original = Headers.empty(); + original.put("Authorization", new ArrayList<>(singletonList("Basic dXNlcjpzZWNyZXQK"))); + + final Map> unit = Headers.immutableCopy(original); + + assertThat(unit.get("Authorization"), hasSize(1)); + + original.get("Authorization").add("Bearer eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.e30."); + + assertThat(unit.get("Authorization"), hasSize(1)); + } + +} diff --git a/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java b/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java index 91d9c9aec..12bce9a98 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/HttpLogWriterTest.java @@ -6,7 +6,7 @@ import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.spy; -public final class HttpLogWriterTest { +final class HttpLogWriterTest { @Test void shouldBeActiveByDefault() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/HttpMessageTest.java b/logbook-api/src/test/java/org/zalando/logbook/HttpMessageTest.java index b2ae617e3..6c35d51c1 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/HttpMessageTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/HttpMessageTest.java @@ -10,7 +10,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public final class HttpMessageTest { +final class HttpMessageTest { @Test void shouldDelegateBodyAsStringToBody() throws IOException { diff --git a/logbook-api/src/test/java/org/zalando/logbook/LogbookFactoryTest.java b/logbook-api/src/test/java/org/zalando/logbook/LogbookFactoryTest.java index 7fb548e7b..9e514a9ae 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/LogbookFactoryTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/LogbookFactoryTest.java @@ -6,7 +6,7 @@ import static org.hamcrest.Matchers.instanceOf; import static org.hamcrest.Matchers.is; -public final class LogbookFactoryTest { +final class LogbookFactoryTest { @Test void shouldLoadInstanceUsingSPI() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java b/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java index 9ed3d79bc..ce5bc35fc 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/LogbookTest.java @@ -18,7 +18,7 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class LogbookTest { +class LogbookTest { private final HeaderFilter headerFilter = mock(HeaderFilter.class); private final QueryFilter queryFilter = mock(QueryFilter.class); diff --git a/logbook-api/src/test/java/org/zalando/logbook/QueryFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/QueryFilterTest.java index ac233ca21..48fa00fc4 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/QueryFilterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/QueryFilterTest.java @@ -6,7 +6,7 @@ import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.is; -public final class QueryFilterTest { +final class QueryFilterTest { @Test void noneShouldDefaultToNoOp() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/RequestFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/RequestFilterTest.java index 4b7cc8d75..8e8e9963c 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/RequestFilterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/RequestFilterTest.java @@ -7,7 +7,7 @@ import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Mockito.mock; -public final class RequestFilterTest { +final class RequestFilterTest { @Test void noneShouldDefaultToNoOp() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java b/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java index 85a705e0a..1e47a8afb 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/RequestURITest.java @@ -15,12 +15,13 @@ import static org.zalando.logbook.RequestURI.Component.SCHEME; import static org.zalando.logbook.RequestURI.reconstruct; -public final class RequestURITest { +final class RequestURITest { private final HttpRequest request = mock(HttpRequest.class); @BeforeEach - public void setUp() { + void setUp() { + when(request.getRequestUri()).thenCallRealMethod(); when(request.getScheme()).thenReturn("http"); when(request.getHost()).thenReturn("localhost"); when(request.getPort()).thenReturn(Optional.empty()); @@ -30,34 +31,34 @@ public void setUp() { @Test void shouldReconstructFully() { - assertThat(reconstruct(request), is("http://localhost/admin?limit=1")); + assertThat(request.getRequestUri(), is("http://localhost/admin?limit=1")); } @Test void shouldNotIncludeStandardHttpPort() { when(request.getScheme()).thenReturn("http"); when(request.getPort()).thenReturn(Optional.of(80)); - assertThat(reconstruct(request), is("http://localhost/admin?limit=1")); + assertThat(request.getRequestUri(), is("http://localhost/admin?limit=1")); } @Test void shouldNotIncludeStandardHttpsPort() { when(request.getScheme()).thenReturn("https"); when(request.getPort()).thenReturn(Optional.of(443)); - assertThat(reconstruct(request), is("https://localhost/admin?limit=1")); + assertThat(request.getRequestUri(), is("https://localhost/admin?limit=1")); } @Test void shouldIncludeNonStandardHttpPort() { when(request.getPort()).thenReturn(Optional.of(8080)); - assertThat(reconstruct(request), is("http://localhost:8080/admin?limit=1")); + assertThat(request.getRequestUri(), is("http://localhost:8080/admin?limit=1")); } @Test void shouldIncludeNonStandardHttpsPort() { when(request.getScheme()).thenReturn("https"); when(request.getPort()).thenReturn(Optional.of(1443)); - assertThat(reconstruct(request), is("https://localhost:1443/admin?limit=1")); + assertThat(request.getRequestUri(), is("https://localhost:1443/admin?limit=1")); } @Test @@ -90,7 +91,7 @@ void shouldReconstructWithoutQuery() { void shouldReconstructWithoutEmptyQuery() { when(request.getQuery()).thenReturn(""); - assertThat(reconstruct(request), is("http://localhost/admin")); + assertThat(request.getRequestUri(), is("http://localhost/admin")); } @Test diff --git a/logbook-api/src/test/java/org/zalando/logbook/ResponseFilterTest.java b/logbook-api/src/test/java/org/zalando/logbook/ResponseFilterTest.java index 98a574606..a2a54fae1 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/ResponseFilterTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/ResponseFilterTest.java @@ -7,7 +7,7 @@ import static org.hamcrest.Matchers.sameInstance; import static org.mockito.Mockito.mock; -public final class ResponseFilterTest { +final class ResponseFilterTest { @Test void noneShouldDefaultToNoOp() { diff --git a/logbook-api/src/test/java/org/zalando/logbook/SinkTest.java b/logbook-api/src/test/java/org/zalando/logbook/SinkTest.java new file mode 100644 index 000000000..43f430ca1 --- /dev/null +++ b/logbook-api/src/test/java/org/zalando/logbook/SinkTest.java @@ -0,0 +1,18 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; +import org.mockito.invocation.InvocationOnMock; + +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; + +class SinkTest { + + @Test + void shouldBeActiveByDefault() { + final Sink unit = mock(Sink.class, InvocationOnMock::callRealMethod); + + assertTrue(unit.isActive()); + } + +} diff --git a/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java b/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java new file mode 100644 index 000000000..9381f24e1 --- /dev/null +++ b/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java @@ -0,0 +1,53 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; +import org.mockito.invocation.InvocationOnMock; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +class StrategyTest { + + private final Strategy unit = mock(Strategy.class, InvocationOnMock::callRealMethod); + private final Precorrelation precorrelation = mock(Precorrelation.class); + private final HttpRequest request = mock(HttpRequest.class); + private final Correlation correlation = mock(Correlation.class); + private final HttpResponse response = mock(HttpResponse.class); + private final Sink sink = mock(Sink.class); + + @Test + void shouldProcessRequestWithBodyByDefault() throws IOException { + unit.process(request); + + verify(request).withBody(); + } + + @Test + void shouldWriteRequestToSinkByDefault() throws IOException { + unit.write(precorrelation, request, sink); + + verify(sink).write(precorrelation, request); + verifyNoMoreInteractions(precorrelation, request); + } + + @Test + void shouldProcessResponseWithBodyByDefault() throws IOException { + unit.process(request, response); + + verify(response).withBody(); + verifyNoMoreInteractions(request); + } + + @Test + void shouldWriteResponseToSinkByDefault() throws IOException { + unit.write(correlation, request, response, sink); + + verify(sink).write(correlation, request, response); + verifyNoMoreInteractions(correlation, request, response); + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/Conditions.java b/logbook-core/src/main/java/org/zalando/logbook/Conditions.java index ddd4cfa51..edab3805e 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/Conditions.java +++ b/logbook-core/src/main/java/org/zalando/logbook/Conditions.java @@ -47,8 +47,9 @@ private static Predicate requestTo(final Function predicate.test(extractor.apply(request)); } - public static Predicate contentType(final String... contentTypes) { - final Predicate query = MediaTypeQuery.compile(contentTypes); + public static Predicate contentType(final String contentType, + final String... contentTypes) { + final Predicate query = MediaTypeQuery.compile(contentType, contentTypes); return message -> query.test(message.getContentType()); diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java index 2df330f52..5f5f26dd2 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java @@ -96,11 +96,6 @@ public Duration getDuration() { return duration; } - @Override - public Correlation correlate() { - return this; - } - } } diff --git a/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java b/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java index 3deb0b981..bf59a27b9 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java +++ b/logbook-core/src/main/java/org/zalando/logbook/HeaderFilters.java @@ -2,10 +2,13 @@ import org.apiguardian.api.API; +import java.util.List; +import java.util.Map; import java.util.function.BiPredicate; import java.util.function.BinaryOperator; import java.util.function.Predicate; +import static java.util.stream.Collectors.toList; import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @@ -36,14 +39,14 @@ public static HeaderFilter replaceHeaders(final BiPredicate pred public static HeaderFilter eachHeader(final BinaryOperator operator) { return headers -> { - final HttpMessage.HeadersBuilder result = new HttpMessage.HeadersBuilder(); + final Map> result = Headers.empty(); - headers.forEach((key, values) -> - values.stream() - .map(value -> operator.apply(key, value)) - .forEach(value -> result.put(key, value))); + headers.forEach((name, values) -> + result.put(name, values.stream() + .map(value -> operator.apply(name, value)) + .collect(toList()))); - return result.build(); + return Headers.immutableCopy(result); }; } @@ -53,14 +56,21 @@ public static HeaderFilter removeHeaders(final Predicate keyPredicate) { public static HeaderFilter removeHeaders(final BiPredicate predicate) { return headers -> { - final HttpMessage.HeadersBuilder result = new HttpMessage.HeadersBuilder(); + final Map> result = Headers.empty(); - headers.forEach((key, values) -> - values.stream() - .filter(value -> !predicate.test(key, value)) - .forEach(value -> result.put(key, value))); + headers.forEach((name, original) -> { + final List values = original.stream() + .filter(value -> !predicate.test(name, value)) + .collect(toList()); - return result.build(); + if (values.isEmpty()) { + return; + } + + result.put(name, values); + }); + + return Headers.immutableCopy(result); }; } diff --git a/logbook-core/src/main/java/org/zalando/logbook/MediaTypeQuery.java b/logbook-core/src/main/java/org/zalando/logbook/MediaTypeQuery.java index 517178451..1c1256897 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/MediaTypeQuery.java +++ b/logbook-core/src/main/java/org/zalando/logbook/MediaTypeQuery.java @@ -14,14 +14,13 @@ private MediaTypeQuery() { } - static Predicate compile(final String... queries) { + static Predicate compile(final String query, final String... queries) { return Arrays.stream(queries) .map(MediaTypeQuery::compile) - .reduce(Predicate::or) - .orElse($ -> false); + .reduce(compile(query), Predicate::or); } - static Predicate compile(final String query) { + private static Predicate compile(final String query) { final int slash = query.indexOf('/'); final int semicolon = query.indexOf(';'); final int end = semicolon == -1 ? query.length() : semicolon; diff --git a/logbook-core/src/main/java/org/zalando/logbook/XmlCompactingBodyFilter.java b/logbook-core/src/main/java/org/zalando/logbook/XmlCompactingBodyFilter.java index 86de060a0..ae3fe2c0e 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/XmlCompactingBodyFilter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/XmlCompactingBodyFilter.java @@ -45,7 +45,7 @@ private String compact(final String body) { final Document document = parseDocument(body); transformer.transform(new DOMSource(document), new StreamResult(output)); return output.toString(); - } catch (Exception e) { + } catch (final Exception e) { log.trace("Unable to compact body, is it a XML?. Keep it as-is: `{}`", e.getMessage()); return body; } diff --git a/logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java similarity index 78% rename from logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java rename to logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java index e4973a835..aa1e2dba8 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java @@ -1,11 +1,11 @@ package org.zalando.logbook; -import lombok.AllArgsConstructor; - import java.io.IOException; -@AllArgsConstructor -public final class BodyOnlyIfErrorStrategy implements Strategy { +/** + * Proof of concept + */ +final class BodyOnlyIfErrorStrategy implements Strategy { @Override public HttpRequest process(final HttpRequest request) throws IOException { @@ -19,7 +19,7 @@ public void write(final Precorrelation precorrelation, final HttpRequest request } @Override - public HttpResponse process(HttpRequest request, final HttpResponse response) throws IOException { + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { return response.withBody(); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/BodyReplacersTest.java b/logbook-core/src/test/java/org/zalando/logbook/BodyReplacersTest.java index 8fd250628..ed323120d 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/BodyReplacersTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/BodyReplacersTest.java @@ -11,7 +11,7 @@ import static org.mockito.Mockito.when; import static org.zalando.logbook.BodyReplacers.replaceBody; -public final class BodyReplacersTest { +final class BodyReplacersTest { @Test void shouldReplaceWith() { diff --git a/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java index ba57b1ee5..c308cb65f 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ChunkingHttpLogWriterTest.java @@ -19,7 +19,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -public final class ChunkingHttpLogWriterTest { +final class ChunkingHttpLogWriterTest { private final HttpLogWriter delegate = mock(HttpLogWriter.class); private final HttpLogWriter unit = new ChunkingHttpLogWriter(20, delegate); diff --git a/logbook-core/src/test/java/org/zalando/logbook/ChunkingSpliteratorTest.java b/logbook-core/src/test/java/org/zalando/logbook/ChunkingSpliteratorTest.java index 8bf000f09..1a4c41a5d 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ChunkingSpliteratorTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ChunkingSpliteratorTest.java @@ -15,7 +15,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; -public final class ChunkingSpliteratorTest { +final class ChunkingSpliteratorTest { @Test void shouldEstimateSizeWithoutTrailingPart() { diff --git a/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java b/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java new file mode 100644 index 000000000..96e3a7392 --- /dev/null +++ b/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java @@ -0,0 +1,73 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.util.Arrays; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class CompositeSinkTest { + + private final Sink first = mock(Sink.class); + private final Sink second = mock(Sink.class); + private final Sink unit = new CompositeSink(Arrays.asList(first, second)); + + private final Precorrelation precorrelation = mock(Precorrelation.class); + private final HttpRequest request = mock(HttpRequest.class); + private final Correlation correlation = mock(Correlation.class); + private final HttpResponse response = mock(HttpResponse.class); + + @Test + void isActiveIfAny() { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(false); + + assertTrue(unit.isActive()); + } + + @Test + void isActiveIfAll() { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(true); + + assertTrue(unit.isActive()); + } + + @Test + void isInactiveIfNone() { + when(first.isActive()).thenReturn(false); + when(second.isActive()).thenReturn(false); + + assertFalse(unit.isActive()); + } + + @Test + void writeRequestToAll() throws IOException { + unit.write(precorrelation, request); + + verify(first).write(precorrelation, request); + verify(second).write(precorrelation, request); + } + + @Test + void writeResponseToAll() throws IOException { + unit.write(correlation, request, response); + + verify(first).write(correlation, request, response); + verify(second).write(correlation, request, response); + } + + @Test + void writeBoth() throws IOException { + unit.writeBoth(correlation, request, response); + + verify(first).writeBoth(correlation, request, response); + verify(second).writeBoth(correlation, request, response); + } + +} diff --git a/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java b/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java index 972523e34..e15ef1780 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ConditionsTest.java @@ -13,7 +13,7 @@ import static org.zalando.logbook.Conditions.requestTo; import static org.zalando.logbook.Conditions.withoutContentType; -public final class ConditionsTest { +final class ConditionsTest { private final MockHttpRequest request = MockHttpRequest.create() .withHeaders(MockHeaders.of("X-Secret", "true")) diff --git a/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java index 854cac0cd..db463e1d2 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/CurlHttpLogFormatterTest.java @@ -13,7 +13,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -public final class CurlHttpLogFormatterTest { +final class CurlHttpLogFormatterTest { @Test void shouldLogRequest() throws IOException { diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java index 69cf35b85..0b57645d6 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogFormatterTest.java @@ -11,7 +11,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public final class DefaultHttpLogFormatterTest { +final class DefaultHttpLogFormatterTest { private final HttpLogFormatter unit = new DefaultHttpLogFormatter(); diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java index c4a80e011..05a46eef2 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterLevelTest.java @@ -18,7 +18,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -public final class DefaultHttpLogWriterLevelTest { +final class DefaultHttpLogWriterLevelTest { static Iterable data() { final Logger logger = mock(Logger.class); diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java index 08f883075..74b40151a 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultHttpLogWriterTest.java @@ -16,7 +16,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; -public final class DefaultHttpLogWriterTest { +final class DefaultHttpLogWriterTest { @Test void shouldDefaultToLogbookLogger() { diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java new file mode 100644 index 000000000..c82146d1d --- /dev/null +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookFactoryTest.java @@ -0,0 +1,20 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.mockito.Mockito.mock; + +class DefaultLogbookFactoryTest { + + private final Logbook logbook = Logbook.create(); + private final HttpRequest request = mock(HttpRequest.class); + private final HttpResponse response = mock(HttpResponse.class); + + @Test + void shouldCreateWithDefaults() throws IOException { + logbook.process(request).write().process(response).write(); + } + +} diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java index 3e3159248..5f5be5bd0 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultLogbookTest.java @@ -6,13 +6,10 @@ import org.mockito.stubbing.Answer; import java.io.IOException; -import java.util.Optional; import java.util.function.Predicate; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; -import static org.hamcrest.Matchers.is; -import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -20,7 +17,7 @@ import static org.mockito.Mockito.when; import static org.mockito.Mockito.withSettings; -public final class DefaultLogbookTest { +final class DefaultLogbookTest { @SuppressWarnings("unchecked") private final Predicate predicate = mock(Predicate.class); @@ -49,7 +46,7 @@ private static Answer delegateTo(final Object delegate) { } @BeforeEach - public void defaultBehaviour() { + void defaultBehaviour() { when(sink.isActive()).thenReturn(true); when(predicate.test(any())).thenReturn(true); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/DefaultSinkTest.java b/logbook-core/src/test/java/org/zalando/logbook/DefaultSinkTest.java new file mode 100644 index 000000000..ed6a63b3d --- /dev/null +++ b/logbook-core/src/test/java/org/zalando/logbook/DefaultSinkTest.java @@ -0,0 +1,61 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class DefaultSinkTest { + + private final HttpLogFormatter formatter = mock(HttpLogFormatter.class); + private final HttpLogWriter writer = mock(HttpLogWriter.class); + private final Sink unit = new DefaultSink(formatter, writer); + + private final Precorrelation precorrelation = mock(Precorrelation.class); + private final HttpRequest request = mock(HttpRequest.class); + private final Correlation correlation = mock(Correlation.class); + private final HttpResponse response = mock(HttpResponse.class); + + @Test + void isActiveIfWriterIsActive() { + when(writer.isActive()).thenReturn(true); + + assertTrue(unit.isActive()); + } + + @Test + void isInactiveIfWriterIsInactive() { + when(writer.isActive()).thenReturn(false); + + assertFalse(unit.isActive()); + } + + @Test + void writeRequest() throws IOException { + when(formatter.format(precorrelation, request)).thenReturn("request"); + unit.write(precorrelation, request); + verify(writer).write(precorrelation, "request"); + } + + @Test + void writeResponse() throws IOException { + when(formatter.format(correlation, response)).thenReturn("response"); + unit.write(correlation, request, response); + verify(writer).write(correlation, "response"); + } + + @Test + void writeBoth() throws IOException { + when(formatter.format(correlation, request)).thenReturn("request"); + when(formatter.format(correlation, response)).thenReturn("response"); + unit.writeBoth(correlation, request, response); + verify(writer).write((Precorrelation) correlation, "request"); + verify(writer).write(correlation, "response"); + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java similarity index 75% rename from logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java rename to logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java index db6e059fd..942478fa8 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java @@ -1,11 +1,11 @@ package org.zalando.logbook; -import lombok.AllArgsConstructor; - import java.io.IOException; -@AllArgsConstructor -public final class ErrorResponseOnlyStrategy implements Strategy { +/** + * Proof of concept + */ +final class ErrorResponseOnlyStrategy implements Strategy { @Override public HttpRequest process(final HttpRequest request) throws IOException { @@ -19,7 +19,7 @@ public void write(final Precorrelation precorrelation, final HttpRequest request } @Override - public HttpResponse process(HttpRequest request, final HttpResponse response) throws IOException { + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { return response.withBody(); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpRequestTest.java b/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpRequestTest.java index fb8b315c0..e94a7f801 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpRequestTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpRequestTest.java @@ -12,7 +12,7 @@ import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; -public final class FilteredHttpRequestTest { +final class FilteredHttpRequestTest { private final HttpRequest unit = new FilteredHttpRequest(MockHttpRequest.create() .withQuery("password=1234&limit=1") diff --git a/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpResponseTest.java b/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpResponseTest.java index 4d05b0c79..f845bad94 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpResponseTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/FilteredHttpResponseTest.java @@ -10,7 +10,7 @@ import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.is; -public final class FilteredHttpResponseTest { +final class FilteredHttpResponseTest { private final HttpResponse unit = new FilteredHttpResponse(MockHttpResponse.create() .withHeaders(MockHeaders.of( diff --git a/logbook-core/src/test/java/org/zalando/logbook/HeaderFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/HeaderFiltersTest.java index cad850ef5..e6e3c1c61 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/HeaderFiltersTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/HeaderFiltersTest.java @@ -16,7 +16,7 @@ import static org.zalando.logbook.HeaderFilters.eachHeader; import static org.zalando.logbook.HeaderFilters.removeHeaders; -public final class HeaderFiltersTest { +final class HeaderFiltersTest { @Test void shouldFilterHeaders() { diff --git a/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java b/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java index 013949547..5900c402e 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/JsonHttpLogFormatterTest.java @@ -22,7 +22,7 @@ import static org.zalando.logbook.Origin.LOCAL; import static org.zalando.logbook.Origin.REMOTE; -public final class JsonHttpLogFormatterTest { +final class JsonHttpLogFormatterTest { private final HttpLogFormatter unit = new JsonHttpLogFormatter(); diff --git a/logbook-core/src/test/java/org/zalando/logbook/MediaTypeQueryTest.java b/logbook-core/src/test/java/org/zalando/logbook/MediaTypeQueryTest.java index 468ef4b72..3e14ae8ec 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/MediaTypeQueryTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/MediaTypeQueryTest.java @@ -8,7 +8,7 @@ import static org.hamcrest.Matchers.is; import static org.zalando.logbook.MediaTypeQuery.compile; -public final class MediaTypeQueryTest { +final class MediaTypeQueryTest { @Test void shouldMatchAllMatch() { diff --git a/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java index b0e020ba0..c601e8101 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/RequestFiltersTest.java @@ -8,7 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public final class RequestFiltersTest { +final class RequestFiltersTest { @Test void shouldReplaceImageBodyByDefault() throws IOException { @@ -26,6 +26,22 @@ void shouldReplaceImageBodyByDefault() throws IOException { assertThat(request.getBodyAsString(), is("")); } + @Test + void shouldReplaceImageBodyEvenWithoutBody() throws IOException { + final RequestFilter filter = RequestFilters.defaultValue(); + + final HttpRequest request = filter.filter(MockHttpRequest.create() + .withContentType("image/png") + .withBodyAsString("this is an image")); + + request.withoutBody(); + + assertThat(request.getContentType(), is("image/png")); + assertThat(request.getContentType(), is("image/png")); + assertThat(request.getBody(), is("".getBytes(UTF_8))); + assertThat(request.getBodyAsString(), is("")); + } + @Test void shouldNotReplaceTextByDefault() throws IOException { final RequestFilter filter = RequestFilters.defaultValue(); diff --git a/logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/RequestOnlyStrategy.java similarity index 60% rename from logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java rename to logbook-core/src/test/java/org/zalando/logbook/RequestOnlyStrategy.java index 4f12825d3..8da913380 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/RequestOnlyStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/RequestOnlyStrategy.java @@ -1,9 +1,12 @@ package org.zalando.logbook; -public final class RequestOnlyStrategy implements Strategy { +/** + * Proof of concept + */ +final class RequestOnlyStrategy implements Strategy { @Override - public HttpResponse process(HttpRequest request, final HttpResponse response) { + public HttpResponse process(final HttpRequest request, final HttpResponse response) { return response.withoutBody(); } diff --git a/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java b/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java index 2bc3cac9d..c91dafaf4 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ResponseFiltersTest.java @@ -8,7 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public final class ResponseFiltersTest { +final class ResponseFiltersTest { @Test void shouldReplaceImageBodyByDefault() throws IOException { @@ -26,6 +26,22 @@ void shouldReplaceImageBodyByDefault() throws IOException { assertThat(response.getBodyAsString(), is("")); } + @Test + void shouldReplaceImageBodyEvenWithoutBody() throws IOException { + final ResponseFilter filter = ResponseFilters.defaultValue(); + + final HttpResponse response = filter.filter(MockHttpResponse.create() + .withContentType("image/png") + .withBodyAsString("this is an image")); + + response.withoutBody(); + + assertThat(response.getContentType(), is("image/png")); + assertThat(response.getContentType(), is("image/png")); + assertThat(response.getBody(), is("".getBytes(UTF_8))); + assertThat(response.getBodyAsString(), is("")); + } + @Test void shouldNotReplaceTextBodyByDefault() throws IOException { final ResponseFilter filter = ResponseFilters.defaultValue(); diff --git a/logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java similarity index 71% rename from logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java rename to logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java index 3b84ddd4a..76a2956b6 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/ResponseOnlyStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java @@ -1,9 +1,9 @@ package org.zalando.logbook; -import lombok.AllArgsConstructor; - -@AllArgsConstructor -public final class ResponseOnlyStrategy implements Strategy { +/** + * Proof of concept + */ +final class ResponseOnlyStrategy implements Strategy { @Override public HttpRequest process(final HttpRequest request) { diff --git a/logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java similarity index 68% rename from logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java rename to logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java index dd961bfe4..a1418ad2c 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java @@ -1,11 +1,11 @@ package org.zalando.logbook; -import lombok.AllArgsConstructor; - import java.io.IOException; -@AllArgsConstructor -public final class SomeRequestsWithoutBodyStrategy implements Strategy { +/** + * Proof of concept + */ +final class SomeRequestsWithoutBodyStrategy implements Strategy { @Override public HttpRequest process(final HttpRequest request) throws IOException { diff --git a/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java b/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java index 14f1805f0..43237ff76 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/StreamHttpLogWriterTest.java @@ -16,7 +16,7 @@ import static org.mockito.Mockito.verify; @NotThreadSafe -public final class StreamHttpLogWriterTest { +final class StreamHttpLogWriterTest { @Test void shouldBeActiveByDefault() { diff --git a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java index 0cee3c090..8c80c8f2d 100644 --- a/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java +++ b/logbook-httpclient/src/main/java/org/zalando/logbook/httpclient/LocalRequest.java @@ -7,6 +7,7 @@ import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; +import org.zalando.logbook.Headers; import org.zalando.logbook.Origin; import java.io.IOException; @@ -15,8 +16,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; import static org.apache.http.util.EntityUtils.toByteArray; final class LocalRequest implements org.zalando.logbook.HttpRequest { @@ -93,13 +98,14 @@ public String getQuery() { @Override public Map> getHeaders() { - final HeadersBuilder builder = new HeadersBuilder(); + final Map> headers = Headers.empty(); - for (final Header header : request.getAllHeaders()) { - builder.put(header.getName(), header.getValue()); - } + Stream.of(request.getAllHeaders()) + .collect(groupingBy(Header::getName, mapping(Header::getValue, toList()))) + .forEach(headers::put); - return builder.build(); + // TODO immutable? + return headers; } @Override @@ -120,11 +126,6 @@ public Charset getCharset() { .orElse(UTF_8); } - @Override - public byte[] getBody() { - return body == null ? new byte[0] : body; - } - @Override public org.zalando.logbook.HttpRequest withBody() throws IOException { if (body == null) { @@ -146,4 +147,9 @@ public org.zalando.logbook.HttpRequest withoutBody() { return this; } + @Override + public byte[] getBody() { + return body == null ? new byte[0] : body; + } + } 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 8a2ba8dbc..8a293c016 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 @@ -5,6 +5,7 @@ import org.apache.http.HttpResponse; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.entity.ContentType; +import org.zalando.logbook.Headers; import org.zalando.logbook.Origin; import javax.annotation.Nullable; @@ -13,8 +14,12 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.stream.Stream; import static java.nio.charset.StandardCharsets.UTF_8; +import static java.util.stream.Collectors.groupingBy; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; import static org.apache.http.util.EntityUtils.toByteArray; final class RemoteResponse implements org.zalando.logbook.HttpResponse { @@ -43,13 +48,14 @@ public int getStatus() { @Override public Map> getHeaders() { - final HeadersBuilder builder = new HeadersBuilder(); + final Map> headers = Headers.empty(); - for (final Header header : response.getAllHeaders()) { - builder.put(header.getName(), header.getValue()); - } + Stream.of(response.getAllHeaders()) + .collect(groupingBy(Header::getName, mapping(Header::getValue, toList()))) + .forEach(headers::put); - return builder.build(); + // TODO immutable? + return headers; } @Override @@ -70,11 +76,6 @@ public Charset getCharset() { .orElse(UTF_8); } - @Override - public byte[] getBody() { - return body == null ? new byte[0] : body; - } - @Override public org.zalando.logbook.HttpResponse withBody() throws IOException { if (body == null) { @@ -103,4 +104,9 @@ public RemoteResponse withoutBody() { return this; } + @Override + public byte[] getBody() { + return body == null ? new byte[0] : body; + } + } diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java index 522ae32b6..477c17448 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/AbstractHttpTest.java @@ -32,14 +32,14 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; -public abstract class AbstractHttpTest { +abstract class AbstractHttpTest { - public final ClientDriver driver = new ClientDriverFactory().createClientDriver(); + final ClientDriver driver = new ClientDriverFactory().createClientDriver(); - protected final HttpLogWriter writer = mock(HttpLogWriter.class); + final HttpLogWriter writer = mock(HttpLogWriter.class); @BeforeEach - public void defaultBehaviour() { + void defaultBehaviour() { when(writer.isActive()).thenReturn(true); } diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/ForwardingHttpAsyncResponseConsumerTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/ForwardingHttpAsyncResponseConsumerTest.java index 36c34ab04..dfba55098 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/ForwardingHttpAsyncResponseConsumerTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/ForwardingHttpAsyncResponseConsumerTest.java @@ -16,7 +16,7 @@ @OhNoYouDidnt @Facepalm -public final class ForwardingHttpAsyncResponseConsumerTest { +final class ForwardingHttpAsyncResponseConsumerTest { private final HttpAsyncResponseConsumer delegate = mock(HttpAsyncResponseConsumer.class); @@ -79,7 +79,7 @@ void testFailed() throws Exception { @Test @SuppressWarnings("ThrowableResultOfMethodCallIgnored") - public void testGetException() throws Exception { + void testGetException() throws Exception { unit.getException(); verify(delegate).getException(); } diff --git a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java index 8b8b561fa..659a29d7e 100644 --- a/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java +++ b/logbook-httpclient/src/test/java/org/zalando/logbook/httpclient/LocalRequestTest.java @@ -21,12 +21,13 @@ import static org.apache.http.util.EntityUtils.toByteArray; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.aMapWithSize; +import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.hasToString; import static org.hamcrest.Matchers.is; import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; -public final class LocalRequestTest { +final class LocalRequestTest { private HttpRequest get(final String uri) { return new HttpGet(uri); @@ -133,4 +134,25 @@ void shouldReadBodyIfPresent() throws IOException { assertThat(new String(toByteArray(delegate.getEntity()), UTF_8), is("Hello, world!")); } + @Test + void shouldReturnEmptyBodyUntilCaptured() throws IOException { + final HttpEntityEnclosingRequest delegate = post("/"); + delegate.setEntity(new StringEntity("Hello, world!", UTF_8)); + + final LocalRequest unit = unit(delegate); + + assertThat(new String(unit.getBody(), UTF_8), is(emptyString())); + assertThat(new String(unit.withBody().getBody(), UTF_8), is("Hello, world!")); + } + + @Test + void shouldBeSafeAgainstCallingWithBodyTwice() throws IOException { + final HttpEntityEnclosingRequest delegate = post("/"); + delegate.setEntity(new StringEntity("Hello, world!", UTF_8)); + + final LocalRequest unit = unit(delegate); + + assertThat(new String(unit.withBody().withBody().getBody(), UTF_8), is("Hello, world!")); + } + } 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 21e6eb41d..a539020ca 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 @@ -12,21 +12,22 @@ import static java.nio.charset.StandardCharsets.ISO_8859_1; import static java.nio.charset.StandardCharsets.UTF_8; +import static org.apache.http.util.EntityUtils.toByteArray; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.nullValue; -public final class RemoteResponseTest { +final class RemoteResponseTest { - private final BasicHttpEntity basicHttpEntity = new BasicHttpEntity(); + 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); @BeforeEach - public void setUpResponseBody() { - basicHttpEntity.setContent(new ByteArrayInputStream("fooBar".getBytes(UTF_8))); - delegate.setEntity(basicHttpEntity); + void setUpResponseBody() { + entity.setContent(new ByteArrayInputStream("Hello, world!".getBytes(UTF_8))); + delegate.setEntity(entity); } @Test @@ -42,7 +43,7 @@ void shouldReturnDefaultCharsetIfNoneGiven() { } @Test - void shouldNotReadEmptyBodyIfNotPresent() throws IOException { + void shouldNotReadNullBodyIfNotPresent() throws IOException { delegate.setEntity(null); assertThat(new String(unit.withBody().getBody(), UTF_8), is(emptyString())); @@ -51,7 +52,7 @@ void shouldNotReadEmptyBodyIfNotPresent() throws IOException { @Test void shouldNotSwallowDelegatesContentEncodingWhenTransformingEntity() throws IOException { - basicHttpEntity.setContentEncoding("gzip"); + entity.setContentEncoding("gzip"); unit.withBody(); @@ -60,7 +61,7 @@ void shouldNotSwallowDelegatesContentEncodingWhenTransformingEntity() throws IOE @Test void shouldNotSwallowDelegatesChunkedFlagWhenTransformingEntity() throws IOException { - basicHttpEntity.setChunked(true); + entity.setChunked(true); unit.withBody(); @@ -69,11 +70,28 @@ void shouldNotSwallowDelegatesChunkedFlagWhenTransformingEntity() throws IOExcep @Test void shouldNotSwallowDelegatesContentTypeWhenTransformingEntity() throws IOException { - basicHttpEntity.setContentType("application/json"); + entity.setContentType("application/json"); unit.withBody(); assertThat(delegate.getEntity().getContentType().getValue(), is("application/json")); } + @Test + void shouldReadBodyIfPresent() throws IOException { + assertThat(new String(unit.withBody().getBody(), UTF_8), is("Hello, world!")); + assertThat(new String(toByteArray(delegate.getEntity()), UTF_8), is("Hello, world!")); + } + + @Test + void shouldReturnEmptyBodyUntilCaptured() throws IOException { + assertThat(new String(unit.getBody(), UTF_8), is(emptyString())); + assertThat(new String(unit.withBody().getBody(), UTF_8), is("Hello, world!")); + } + + @Test + void shouldBeSafeAgainstCallingWithBodyTwice() throws IOException { + assertThat(new String(unit.withBody().withBody().getBody(), UTF_8), is("Hello, world!")); + } + } diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java index f415040b7..ca9371ed6 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/RemoteRequest.java @@ -92,7 +92,6 @@ public HttpRequest withBody() throws IOException { this.body = ByteStreams.toByteArray(context.getEntityStream()); context.setEntityStream(new ByteArrayInputStream(body)); } - return this; } diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerTest.java new file mode 100644 index 000000000..001790e3f --- /dev/null +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerTest.java @@ -0,0 +1,316 @@ +package org.zalando.logbook.jaxrs; + +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.MultiPart; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Logbook; +import org.zalando.logbook.Sink; +import org.zalando.logbook.jaxrs.testing.support.TestModel; +import org.zalando.logbook.jaxrs.testing.support.TestWebService; + +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Variant; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Locale; +import java.util.Optional; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.ws.rs.client.Entity.entity; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA_TYPE; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.zalando.logbook.BodyReplacers.stream; +import static org.zalando.logbook.Origin.LOCAL; +import static org.zalando.logbook.Origin.REMOTE; +import static org.zalando.logbook.RequestFilters.replaceBody; + +/** + * This test starts in in-memory server with a Logbook server filter. The test + * configures a mock Logbook writer which captures the HttpRequest with HttpResponse objects. + * The test asserts values on these objects to verify the mapping to/from JAX-RS + * ContainerRequestContext with ContainerResponseContext is working as expected. + *

+ * Similarly on the client, the test registers a Logbook client filter configured with a mock writer. + * The test asserts values on the HttpRequest with HttpResponse objects to verify the mapping + * to/from JAX-RS ClientRequestContext with ClientResponseContext. + */ +final class ClientAndServerTest extends JerseyTest { + + private Sink client; + private Sink server; + + ClientAndServerTest() { + forceSet(TestProperties.CONTAINER_PORT, "0"); + } + + @Override + protected Application configure() { + // jersey calls this method within the constructor before our fields are initialized... WTF + this.client = mock(Sink.class); + this.server = mock(Sink.class); + + return new ResourceConfig(TestWebService.class) + .register(new LogbookServerFilter( + Logbook.builder() + // do not replace multi-part form bodies, which is the default + .requestFilter(replaceBody(stream())) + .strategy(new WithBodyStrategy()) + .sink(server) + .build())) + .register(MultiPartFeature.class); + } + + @BeforeEach + void beforeEach() throws Exception { + super.setUp(); + + when(client.isActive()).thenReturn(true); + when(server.isActive()).thenReturn(true); + + getClient() + .register(new LogbookClientFilter( + Logbook.builder() + // do not replace multi-part form bodies, which is the default + .requestFilter(replaceBody(stream())) + .strategy(new WithBodyStrategy()) + .sink(client) + .build())) + .register(MultiPartFeature.class); + } + + @Test + void getWithPathAndQueryParamReturningTextPlain() throws Exception { + final String result = target("testws/testGet/first/textPlain") + .queryParam("param2", "second") + .request() + .get(String.class); + + final RoundTrip roundTrip = getRoundTrip(); + final HttpRequest clientRequest = roundTrip.getClientRequest(); + final HttpResponse clientResponse = roundTrip.getClientResponse(); + final HttpRequest serverRequest = roundTrip.getServerRequest(); + final HttpResponse serverResponse = roundTrip.getServerResponse(); + + assertEquals("param1=first param2=second", result); + + // client request + assertEquals("HTTP/1.1", clientRequest.getProtocolVersion()); + assertEquals("GET", clientRequest.getMethod()); + assertEquals(LOCAL, clientRequest.getOrigin()); + assertEquals("localhost", clientRequest.getRemote()); + assertEquals("http", clientRequest.getScheme()); + assertEquals("localhost", clientRequest.getHost()); + assertEquals(Optional.of(this.getPort()), clientRequest.getPort()); + assertEquals("/testws/testGet/first/textPlain", clientRequest.getPath()); + assertEquals("param2=second", clientRequest.getQuery()); + + // client response + assertEquals("HTTP/1.1", clientResponse.getProtocolVersion()); + assertEquals("text/plain", clientResponse.getHeaders().get("Content-type").get(0)); + assertEquals("26", clientResponse.getHeaders().get("Content-length").get(0)); + assertEquals("param1=first param2=second", clientResponse.getBodyAsString()); + assertEquals("text/plain", clientResponse.getContentType()); + assertEquals("HTTP/1.1", clientResponse.getProtocolVersion()); + assertEquals(200, clientResponse.getStatus()); + assertEquals(REMOTE, clientResponse.getOrigin()); + assertEquals(UTF_8, clientResponse.getCharset()); + + // server request + assertEquals("HTTP/1.1", serverRequest.getProtocolVersion()); + assertEquals("GET", serverRequest.getMethod()); + assertEquals(REMOTE, serverRequest.getOrigin()); + assertEquals("localhost:" + this.getPort(), serverRequest.getRemote()); + assertEquals("http", serverRequest.getScheme()); + assertEquals("localhost", serverRequest.getHost()); + assertEquals(Optional.of(this.getPort()), serverRequest.getPort()); + assertEquals("/testws/testGet/first/textPlain", serverRequest.getPath()); + assertEquals("param2=second", serverRequest.getQuery()); + assertThat("serverRequest userAgent", serverRequest.getHeaders().get("User-Agent").get(0), + containsString("Jersey")); + + // server response + assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()); + assertEquals("text/plain", serverResponse.getHeaders().get("Content-type").get(0)); + assertEquals("param1=first param2=second", serverResponse.getBodyAsString()); + assertEquals("text/plain", serverResponse.getContentType()); + assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()); + assertEquals(200, serverResponse.getStatus()); + assertEquals(LOCAL, serverResponse.getOrigin()); + assertEquals(UTF_8, serverResponse.getCharset()); + } + + @Test + void multiPartFormDataAndSimulatedFileUpload() throws IOException { + final MultiPart multiPart = new MultiPart(); + multiPart.setMediaType(MULTIPART_FORM_DATA_TYPE); + final String result = target("testws/testPostForm").request().post( + entity(multiPart.bodyPart(new StreamDataBodyPart("testFileFormField", + new ByteArrayInputStream("I am text file content".getBytes(UTF_8)), + "testUploadedFilename", + TEXT_PLAIN_TYPE + )) + .bodyPart(new FormDataBodyPart("name", "nameValue!@#$%")) + .bodyPart(new FormDataBodyPart("age", "-99")), multiPart.getMediaType()), + String.class + ); + + final RoundTrip roundTrip = getRoundTrip(); + final HttpRequest clientRequest = roundTrip.getClientRequest(); + final HttpResponse clientResponse = roundTrip.getClientResponse(); + final HttpRequest serverRequest = roundTrip.getServerRequest(); + final HttpResponse serverResponse = roundTrip.getServerResponse(); + + assertEquals("name was nameValue!@#$% age was -99 file was I am text file content", result); + + // client request + assertEquals("HTTP/1.1", clientRequest.getProtocolVersion()); + assertEquals("POST", clientRequest.getMethod()); + assertEquals(LOCAL, clientRequest.getOrigin()); + assertEquals("localhost", clientRequest.getRemote()); + assertEquals("http", clientRequest.getScheme()); + assertEquals("localhost", clientRequest.getHost()); + assertEquals(Optional.of(this.getPort()), clientRequest.getPort()); + assertEquals("/testws/testPostForm", clientRequest.getPath()); + assertEquals("", clientRequest.getQuery()); + assertNotEquals("", clientRequest.getBodyAsString()); + + // client response + assertEquals("HTTP/1.1", clientResponse.getProtocolVersion()); + assertEquals("text/plain", clientResponse.getHeaders().get("Content-type").get(0)); + assertEquals("67", clientResponse.getHeaders().get("Content-length").get(0)); + assertEquals("name was nameValue!@#$% age was -99 file was I am text file content", + clientResponse.getBodyAsString()); + assertEquals("text/plain", clientResponse.getContentType()); + assertEquals("HTTP/1.1", clientResponse.getProtocolVersion()); + assertEquals(200, clientResponse.getStatus()); + assertEquals(REMOTE, clientResponse.getOrigin()); + assertEquals(UTF_8, clientResponse.getCharset()); + + // server request + assertEquals("HTTP/1.1", serverRequest.getProtocolVersion()); + assertEquals("POST", serverRequest.getMethod()); + assertEquals(REMOTE, serverRequest.getOrigin()); + assertEquals("localhost:" + this.getPort(), serverRequest.getRemote()); + assertEquals("http", serverRequest.getScheme()); + assertEquals("localhost", serverRequest.getHost()); + assertEquals(Optional.of(this.getPort()), serverRequest.getPort()); + assertEquals("/testws/testPostForm", serverRequest.getPath()); + assertEquals("", serverRequest.getQuery()); + assertThat("serverRequest userAgent", serverRequest.getHeaders().get("User-Agent").get(0), + containsString("Jersey")); + assertNotEquals("", serverRequest.getBodyAsString()); + + // server response + assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()); + assertEquals("text/plain", serverResponse.getHeaders().get("Content-type").get(0)); + assertEquals("name was nameValue!@#$% age was -99 file was I am text file content", + serverResponse.getBodyAsString()); + assertEquals("text/plain", serverResponse.getContentType()); + assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()); + assertEquals(200, serverResponse.getStatus()); + assertEquals(LOCAL, serverResponse.getOrigin()); + assertEquals(UTF_8, serverResponse.getCharset()); + } + + @Test + void putJsonPayloadReturningJsonPayload() throws IOException { + target("testws/testPutJson") + .request() + .put(entity( + new TestModel().setProperty1("val1").setProperty2("val2"), + new Variant( + APPLICATION_JSON_TYPE, Locale.CANADA, "utf-8" + )) + ); + + final RoundTrip roundTrip = getRoundTrip(); + final HttpRequest clientRequest = roundTrip.getClientRequest(); + final HttpResponse clientResponse = roundTrip.getClientResponse(); + final HttpRequest serverRequest = roundTrip.getServerRequest(); + final HttpResponse serverResponse = roundTrip.getServerResponse(); + + // client request + assertEquals("PUT", clientRequest.getMethod()); + assertEquals(LOCAL, clientRequest.getOrigin()); + assertEquals("localhost", clientRequest.getRemote()); + assertEquals("http", clientRequest.getScheme()); + assertEquals("localhost", clientRequest.getHost()); + assertEquals(Optional.of(this.getPort()), clientRequest.getPort()); + assertEquals("/testws/testPutJson", clientRequest.getPath()); + assertEquals("", clientRequest.getQuery()); + assertEquals("{\"property1\":\"val1\",\"property2\":\"val2\"}", clientRequest.getBodyAsString()); + + // client response + assertEquals("", clientResponse.getBodyAsString()); + assertEquals("HTTP/1.1", clientResponse.getProtocolVersion()); + assertEquals(204, clientResponse.getStatus()); + assertEquals(REMOTE, clientResponse.getOrigin()); + assertEquals(UTF_8, clientResponse.getCharset()); + + // server request + assertEquals("PUT", serverRequest.getMethod()); + assertEquals(REMOTE, serverRequest.getOrigin()); + assertEquals("{\"property1\":\"val1\",\"property2\":\"val2\"}", serverRequest.getBodyAsString()); + assertEquals("localhost:" + this.getPort(), serverRequest.getRemote()); + assertEquals("http", serverRequest.getScheme()); + assertEquals("localhost", serverRequest.getHost()); + assertEquals(Optional.of(this.getPort()), serverRequest.getPort()); + assertEquals("/testws/testPutJson", serverRequest.getPath()); + assertEquals("", serverRequest.getQuery()); + assertThat("serverRequest userAgent", serverRequest.getHeaders().get("User-Agent").get(0), + containsString("Jersey")); + + // server response + assertEquals("", serverResponse.getBodyAsString()); + assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()); + assertEquals(204, serverResponse.getStatus()); + assertEquals(LOCAL, serverResponse.getOrigin()); + assertEquals(UTF_8, serverResponse.getCharset()); + } + + private RoundTrip getRoundTrip() throws IOException { + return new RoundTrip( + captureRequest(client), + captureResponse(client), + captureRequest(server), + captureResponse(server) + ); + } + + private static HttpRequest captureRequest(final Sink sink) throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(sink).write(any(), captor.capture()); + return captor.getValue(); + } + + private static HttpResponse captureResponse(final Sink sink) throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(sink).write(any(), any(), captor.capture()); + return captor.getValue(); + } + + @AfterEach + void afterEach() throws Exception { + super.tearDown(); + } + +} diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerWithoutBodyTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerWithoutBodyTest.java new file mode 100644 index 000000000..39e6d14ff --- /dev/null +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/ClientAndServerWithoutBodyTest.java @@ -0,0 +1,167 @@ +package org.zalando.logbook.jaxrs; + +import org.glassfish.jersey.media.multipart.FormDataBodyPart; +import org.glassfish.jersey.media.multipart.MultiPart; +import org.glassfish.jersey.media.multipart.MultiPartFeature; +import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; +import org.glassfish.jersey.server.ResourceConfig; +import org.glassfish.jersey.test.JerseyTest; +import org.glassfish.jersey.test.TestProperties; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.mockito.ArgumentCaptor; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Logbook; +import org.zalando.logbook.Sink; +import org.zalando.logbook.jaxrs.testing.support.TestModel; +import org.zalando.logbook.jaxrs.testing.support.TestWebService; + +import javax.ws.rs.core.Application; +import javax.ws.rs.core.Variant; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.util.Locale; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static javax.ws.rs.client.Entity.entity; +import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; +import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA_TYPE; +import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.zalando.logbook.BodyReplacers.stream; +import static org.zalando.logbook.RequestFilters.replaceBody; + +final class ClientAndServerWithoutBodyTest extends JerseyTest { + + private Sink client; + private Sink server; + + ClientAndServerWithoutBodyTest() { + forceSet(TestProperties.CONTAINER_PORT, "0"); + } + + @Override + protected Application configure() { + // jersey calls this method within the constructor before our fields are initialized... WTF + this.client = mock(Sink.class); + this.server = mock(Sink.class); + + return new ResourceConfig(TestWebService.class) + .register(new LogbookServerFilter( + Logbook.builder() + // do not replace multi-part form bodies, which is the default + .requestFilter(replaceBody(stream())) + .strategy(new WithoutBodyStrategy()) + .sink(server) + .build())) + .register(MultiPartFeature.class); + } + + @BeforeEach + void beforeEach() throws Exception { + super.setUp(); + + when(client.isActive()).thenReturn(true); + when(server.isActive()).thenReturn(true); + + getClient() + .register(new LogbookClientFilter( + Logbook.builder() + // do not replace multi-part form bodies, which is the default + .requestFilter(replaceBody(stream())) + .strategy(new WithoutBodyStrategy()) + .sink(client) + .build())) + .register(MultiPartFeature.class); + } + + @Test + void getWithPathAndQueryParamReturningTextPlain() throws Exception { + target("testws/testGet/first/textPlain") + .queryParam("param2", "second") + .request() + .get(String.class); + + final RoundTrip roundTrip = getRoundTrip(); + + assertEquals("", roundTrip.getClientRequest().getBodyAsString()); + assertEquals("", roundTrip.getClientResponse().getBodyAsString()); + assertEquals("", roundTrip.getServerRequest().getBodyAsString()); + assertEquals("", roundTrip.getServerResponse().getBodyAsString()); + } + + @Test + void multiPartFormDataAndSimulatedFileUpload() throws IOException { + final MultiPart multiPart = new MultiPart(); + multiPart.setMediaType(MULTIPART_FORM_DATA_TYPE); + target("testws/testPostForm").request().post( + entity(multiPart.bodyPart(new StreamDataBodyPart("testFileFormField", + new ByteArrayInputStream("I am text file content".getBytes(UTF_8)), + "testUploadedFilename", + TEXT_PLAIN_TYPE + )) + .bodyPart(new FormDataBodyPart("name", "nameValue!@#$%")) + .bodyPart(new FormDataBodyPart("age", "-99")), multiPart.getMediaType()), + String.class + ); + + final RoundTrip roundTrip = getRoundTrip(); + + assertEquals("", roundTrip.getClientRequest().getBodyAsString()); + assertEquals("", roundTrip.getClientResponse().getBodyAsString()); + assertEquals("", roundTrip.getServerRequest().getBodyAsString()); + assertEquals("", roundTrip.getServerResponse().getBodyAsString()); + } + + @Test + void putJsonPayloadReturningJsonPayload() throws IOException { + target("testws/testPutJson") + .request() + .put(entity( + new TestModel().setProperty1("val1").setProperty2("val2"), + new Variant( + APPLICATION_JSON_TYPE, Locale.CANADA, "utf-8" + )) + ); + + final RoundTrip roundTrip = getRoundTrip(); + + assertEquals("", roundTrip.getClientRequest().getBodyAsString()); + assertEquals("", roundTrip.getClientResponse().getBodyAsString()); + assertEquals("", roundTrip.getServerRequest().getBodyAsString()); + assertEquals("", roundTrip.getServerResponse().getBodyAsString()); + } + + private RoundTrip getRoundTrip() throws IOException { + return new RoundTrip( + captureRequest(client), + captureResponse(client), + captureRequest(server), + captureResponse(server) + ); + } + + private static HttpRequest captureRequest(final Sink sink) throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); + verify(sink).write(any(), captor.capture()); + return captor.getValue(); + } + + private static HttpResponse captureResponse(final Sink sink) throws IOException { + final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); + verify(sink).write(any(), any(), captor.capture()); + return captor.getValue(); + } + + @AfterEach + void afterEach() throws Exception { + super.tearDown(); + } + +} diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/HttpMessagesTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/HttpMessagesTest.java index b2219fdce..0013d79a4 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/HttpMessagesTest.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/HttpMessagesTest.java @@ -8,16 +8,16 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.zalando.logbook.jaxrs.HttpMessages.getPort; -public class HttpMessagesTest { +class HttpMessagesTest { @Test - public void shouldReturnPort() throws Exception { + void shouldReturnPort() throws Exception { final URI uri = new URI("http://localhost:99999"); assertEquals(Optional.of(99999), getPort(uri)); } @Test - public void shouldReturnEmptyForAbsentPort() throws Exception { + void shouldReturnEmptyForAbsentPort() throws Exception { final URI uri = new URI("http://localhost"); assertEquals(Optional.empty(), getPort(uri)); } diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java deleted file mode 100644 index 2d579c39d..000000000 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/JerseyClientAndServerTest.java +++ /dev/null @@ -1,325 +0,0 @@ -package org.zalando.logbook.jaxrs; - -import org.glassfish.jersey.media.multipart.FormDataBodyPart; -import org.glassfish.jersey.media.multipart.MultiPart; -import org.glassfish.jersey.media.multipart.MultiPartFeature; -import org.glassfish.jersey.media.multipart.file.StreamDataBodyPart; -import org.glassfish.jersey.server.ResourceConfig; -import org.glassfish.jersey.test.JerseyTest; -import org.glassfish.jersey.test.TestProperties; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.zalando.logbook.HttpRequest; -import org.zalando.logbook.HttpResponse; -import org.zalando.logbook.Logbook; -import org.zalando.logbook.Sink; -import org.zalando.logbook.jaxrs.testing.support.TestModel; -import org.zalando.logbook.jaxrs.testing.support.TestWebService; - -import javax.ws.rs.core.Application; -import javax.ws.rs.core.Variant; -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.util.Locale; -import java.util.Optional; - -import static java.nio.charset.StandardCharsets.UTF_8; -import static javax.ws.rs.client.Entity.entity; -import static javax.ws.rs.core.MediaType.APPLICATION_JSON_TYPE; -import static javax.ws.rs.core.MediaType.MULTIPART_FORM_DATA_TYPE; -import static javax.ws.rs.core.MediaType.TEXT_PLAIN_TYPE; -import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.zalando.logbook.BodyReplacers.stream; -import static org.zalando.logbook.Origin.LOCAL; -import static org.zalando.logbook.Origin.REMOTE; -import static org.zalando.logbook.RequestFilters.replaceBody; - -/** - * This test starts in in-memory server with a Logbook server filter. The test - * configures a mock Logbook writer which captures the HttpRequest with HttpResponse objects. - * The test asserts values on these objects to verify the mapping to/from JAX-RS - * ContainerRequestContext with ContainerResponseContext is working as expected. - *

- * Similarly on the client, the test registers a Logbook client filter configured with a mock writer. - * The test asserts values on the HttpRequest with HttpResponse objects to verify the mapping - * to/from JAX-RS ClientRequestContext with ClientResponseContext. - */ -public final class JerseyClientAndServerTest extends JerseyTest { - - private Sink client; - private Sink server; - - public JerseyClientAndServerTest() { - forceSet(TestProperties.CONTAINER_PORT, "0"); - } - - @Override - protected Application configure() { - // jersey calls this method within the constructor before our fields are initialized... WTF - this.client = mock(Sink.class); - this.server = mock(Sink.class); - - return new ResourceConfig(TestWebService.class) - .register(new LogbookServerFilter( - Logbook.builder() - // do not replace multi-part form bodies, which is the default - .requestFilter(replaceBody(stream())) - .sink(server) - .build())) - .register(MultiPartFeature.class); - } - - @BeforeEach - public void beforeEach() throws Exception { - super.setUp(); - - when(client.isActive()).thenReturn(true); - when(server.isActive()).thenReturn(true); - - getClient() - .register(new LogbookClientFilter( - Logbook.builder() - // do not replace multi-part form bodies, which is the default - .requestFilter(replaceBody(stream())) - .sink(client) - .build())) - .register(MultiPartFeature.class); - } - - @Test - public void getWithPathAndQueryParamReturningTextPlain() throws Exception { - final String result = target("testws/testGet/first/textPlain") - .queryParam("param2", "second") - .request() - .get(String.class); - - final RoundTrip roundTrip = getRoundTrip(); - final HttpRequest clientRequest = roundTrip.getClientRequest(); - final HttpResponse clientResponse = roundTrip.getClientResponse(); - final HttpRequest serverRequest = roundTrip.getServerRequest(); - final HttpResponse serverResponse = roundTrip.getServerResponse(); - - assertAll( - () -> assertEquals("param1=first param2=second", result), - - // client request - () -> assertEquals("GET", clientRequest.getMethod(), "clientRequest method"), - () -> assertEquals(LOCAL, clientRequest.getOrigin(), "clientRequest origin"), - () -> assertEquals("localhost", clientRequest.getRemote()), - () -> assertEquals("http", clientRequest.getScheme(), "clientRequest scheme"), - () -> assertEquals("localhost", clientRequest.getHost(), "clientRequest host"), - () -> assertEquals(Optional.of(this.getPort()), clientRequest.getPort()), - () -> assertEquals("/testws/testGet/first/textPlain", clientRequest.getPath(), "clientRequest path"), - () -> assertEquals("param2=second", clientRequest.getQuery(), "clientRequest query"), - - // client response - () -> assertEquals("text/plain", clientResponse.getHeaders().get("Content-type").get(0), - "clientResponse contentType"), - () -> assertEquals("26", clientResponse.getHeaders().get("Content-length").get(0), - "clientResponse contentLength"), - () -> assertEquals("param1=first param2=second", clientResponse.getBodyAsString(), - "client response body"), - () -> assertEquals("text/plain", clientResponse.getContentType(), "clientResponse contentType"), - () -> assertEquals("HTTP/1.1", clientResponse.getProtocolVersion(), "clientResponse protocolVersion"), - () -> assertEquals(200, clientResponse.getStatus(), "clientResponse status"), - () -> assertEquals(REMOTE, clientResponse.getOrigin(), "clientResponse origin"), - () -> assertEquals(UTF_8, clientResponse.getCharset(), "clientResponse charset"), - - // server request - () -> assertEquals("GET", serverRequest.getMethod(), "serverRequest method"), - () -> assertEquals(REMOTE, serverRequest.getOrigin(), "serverRequest origin"), - () -> assertEquals("localhost:" + this.getPort(), serverRequest.getRemote()), - () -> assertEquals("http", serverRequest.getScheme(), "serverRequest scheme"), - () -> assertEquals("localhost", serverRequest.getHost(), "serverRequest host"), - () -> assertEquals(Optional.of(this.getPort()), serverRequest.getPort()), - () -> assertEquals("/testws/testGet/first/textPlain", serverRequest.getPath(), "serverRequest path"), - () -> assertEquals("param2=second", serverRequest.getQuery(), "serverRequest method"), - () -> assertThat("serverRequest userAgent", serverRequest.getHeaders().get("User-Agent").get(0), - containsString("Jersey")), - - // server response - () -> assertEquals("text/plain", serverResponse.getHeaders().get("Content-type").get(0), - "serverResponse contentType"), - () -> assertEquals("param1=first param2=second", serverResponse.getBodyAsString(), - "server response body"), - () -> assertEquals("text/plain", serverResponse.getContentType()), - () -> assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()), - () -> assertEquals(200, serverResponse.getStatus()), - () -> assertEquals(LOCAL, serverResponse.getOrigin()), - () -> assertEquals(UTF_8, serverResponse.getCharset()) - ); - } - - @Test - public void multiPartFormDataAndSimulatedFileUpload() throws IOException { - final MultiPart multiPart = new MultiPart(); - multiPart.setMediaType(MULTIPART_FORM_DATA_TYPE); - final String result = target("testws/testPostForm").request().post( - entity(multiPart.bodyPart(new StreamDataBodyPart("testFileFormField", - new ByteArrayInputStream("I am text file content".getBytes(UTF_8)), - "testUploadedFilename", - TEXT_PLAIN_TYPE - )) - .bodyPart(new FormDataBodyPart("name", "nameValue!@#$%")) - .bodyPart(new FormDataBodyPart("age", "-99")), multiPart.getMediaType()), - String.class - ); - - final RoundTrip roundTrip = getRoundTrip(); - final HttpRequest clientRequest = roundTrip.getClientRequest(); - final HttpResponse clientResponse = roundTrip.getClientResponse(); - final HttpRequest serverRequest = roundTrip.getServerRequest(); - final HttpResponse serverResponse = roundTrip.getServerResponse(); - - assertAll( - () -> assertEquals("name was nameValue!@#$% age was -99 file was I am text file content", result, - "result"), - - // client request - () -> assertEquals("POST", clientRequest.getMethod(), "clientRequest method"), - () -> assertEquals(LOCAL, clientRequest.getOrigin(), "clientRequest origin"), - () -> assertEquals("localhost", clientRequest.getRemote()), - () -> assertEquals("http", clientRequest.getScheme(), "clientRequest scheme"), - () -> assertEquals("localhost", clientRequest.getHost(), "clientRequest host"), - () -> assertEquals(Optional.of(this.getPort()), clientRequest.getPort()), - () -> assertEquals("/testws/testPostForm", clientRequest.getPath(), "clientRequest path"), - () -> assertEquals("", clientRequest.getQuery(), "clientRequest query"), - () -> assertNotEquals("", clientRequest.getBodyAsString(), "client request body"), - - // client response - () -> assertEquals("text/plain", clientResponse.getHeaders().get("Content-type").get(0), - "clientResponse contentType"), - () -> assertEquals("67", clientResponse.getHeaders().get("Content-length").get(0), - "clientResponse contentLength"), - () -> assertEquals("name was nameValue!@#$% age was -99 file was I am text file content", - clientResponse.getBodyAsString(), "client response body"), - () -> assertEquals("text/plain", clientResponse.getContentType(), "clientResponse contentType"), - () -> assertEquals("HTTP/1.1", clientResponse.getProtocolVersion(), "clientResponse protocolVersion"), - () -> assertEquals(200, clientResponse.getStatus(), "clientResponse status"), - () -> assertEquals(REMOTE, clientResponse.getOrigin(), "clientResponse origin"), - () -> assertEquals(UTF_8, clientResponse.getCharset(), "clientResponse charset"), - - // server request - () -> assertEquals("POST", serverRequest.getMethod(), "serverRequest method"), - () -> assertEquals(REMOTE, serverRequest.getOrigin(), "serverRequest origin"), - () -> assertEquals("localhost:" + this.getPort(), serverRequest.getRemote()), - () -> assertEquals("http", serverRequest.getScheme(), "serverRequest scheme"), - () -> assertEquals("localhost", serverRequest.getHost(), "serverRequest host"), - () -> assertEquals(Optional.of(this.getPort()), serverRequest.getPort(), "serverRequest port"), - () -> assertEquals("/testws/testPostForm", serverRequest.getPath(), "serverRequest path"), - () -> assertEquals("", serverRequest.getQuery(), "serverRequest method"), - () -> assertThat("serverRequest userAgent", serverRequest.getHeaders().get("User-Agent").get(0), - containsString("Jersey")), - () -> assertNotEquals("", serverRequest.getBodyAsString(), "server request body"), - - // server response - () -> assertEquals("text/plain", serverResponse.getHeaders().get("Content-type").get(0), - "serverResponse contentType"), - () -> assertEquals("name was nameValue!@#$% age was -99 file was I am text file content", - serverResponse.getBodyAsString(), - "server response body"), - () -> assertEquals("text/plain", serverResponse.getContentType()), - () -> assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()), - () -> assertEquals(200, serverResponse.getStatus()), - () -> assertEquals(LOCAL, serverResponse.getOrigin()), - () -> assertEquals(UTF_8, serverResponse.getCharset()) - ); - } - - @Test - public void putJsonPayloadReturningJsonPayload() throws IOException { - target("testws/testPutJson") - .request() - .put(entity( - new TestModel().setProperty1("val1").setProperty2("val2"), - new Variant( - APPLICATION_JSON_TYPE, Locale.CANADA, "utf-8" - )) - ); - - final RoundTrip roundTrip = getRoundTrip(); - final HttpRequest clientRequest = roundTrip.getClientRequest(); - final HttpResponse clientResponse = roundTrip.getClientResponse(); - final HttpRequest serverRequest = roundTrip.getServerRequest(); - final HttpResponse serverResponse = roundTrip.getServerResponse(); - - assertAll( - // client request - () -> assertEquals("PUT", clientRequest.getMethod(), "clientRequest method"), - () -> assertEquals(LOCAL, clientRequest.getOrigin(), "clientRequest origin"), - () -> assertEquals("localhost", clientRequest.getRemote()), - () -> assertEquals("http", clientRequest.getScheme(), "clientRequest scheme"), - () -> assertEquals("localhost", clientRequest.getHost(), "clientRequest host"), - () -> assertEquals(Optional.of(this.getPort()), clientRequest.getPort()), - () -> assertEquals("/testws/testPutJson", clientRequest.getPath(), "clientRequest path"), - () -> assertEquals("", clientRequest.getQuery(), "clientRequest query"), - () -> assertEquals("{\"property1\":\"val1\",\"property2\":\"val2\"}", clientRequest.getBodyAsString(), - "client request body"), - - // client response - () -> assertEquals("", clientResponse.getBodyAsString(), "client response body"), - () -> assertEquals("HTTP/1.1", clientResponse.getProtocolVersion(), "clientResponse protocolVersion"), - () -> assertEquals(204, clientResponse.getStatus(), "clientResponse status"), - () -> assertEquals(REMOTE, clientResponse.getOrigin(), "clientResponse origin"), - () -> assertEquals(UTF_8, clientResponse.getCharset(), "clientResponse charset"), - - // server request - () -> assertEquals("PUT", serverRequest.getMethod(), "serverRequest method"), - () -> assertEquals(REMOTE, serverRequest.getOrigin(), "serverRequest origin"), - () -> assertEquals("{\"property1\":\"val1\",\"property2\":\"val2\"}", serverRequest.getBodyAsString(), - "server request body"), - () -> assertEquals("localhost:" + this.getPort(), serverRequest.getRemote()), - () -> assertEquals("http", serverRequest.getScheme(), "serverRequest scheme"), - () -> assertEquals("localhost", serverRequest.getHost(), "serverRequest host"), - () -> assertEquals(Optional.of(this.getPort()), serverRequest.getPort(), "serverRequest port"), - () -> assertEquals("/testws/testPutJson", serverRequest.getPath(), "serverRequest path"), - () -> assertEquals("", serverRequest.getQuery(), "serverRequest method"), - () -> assertThat("serverRequest userAgent", serverRequest.getHeaders().get("User-Agent").get(0), - containsString("Jersey")), - - // server response - () -> assertEquals("", serverResponse.getBodyAsString(), "server response body"), - () -> assertEquals("HTTP/1.1", serverResponse.getProtocolVersion()), - () -> assertEquals(204, serverResponse.getStatus()), - () -> assertEquals(LOCAL, serverResponse.getOrigin()), - () -> assertEquals(UTF_8, serverResponse.getCharset()) - ); - } - - private RoundTrip getRoundTrip() throws IOException { - return new RoundTrip( - captureRequest(client), - captureResponse(client), - captureRequest(server), - captureResponse(server) - ); - } - - private static HttpRequest captureRequest(final Sink sink) throws IOException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); - verify(sink).write(any(), captor.capture()); - return captor.getValue(); - } - - private static HttpResponse captureResponse(final Sink sink) throws IOException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); - verify(sink).write(any(), any(), captor.capture()); - return captor.getValue(); - } - - @AfterEach - public void afterEach() throws Exception { - super.tearDown(); - } - -} diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookClientFilterTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookClientFilterTest.java index 2cf177350..7d8925bba 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookClientFilterTest.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookClientFilterTest.java @@ -9,7 +9,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; -public final class LogbookClientFilterTest { +final class LogbookClientFilterTest { private final ClientRequestContext request = mock(ClientRequestContext.class); private final ClientResponseContext response = mock(ClientResponseContext.class); @@ -18,7 +18,7 @@ public final class LogbookClientFilterTest { private final LogbookClientFilter unit = new LogbookClientFilter(logbook); @Test - public void filterShouldDoNothingIfCorrelatorIsNotPresent() { + void filterShouldDoNothingIfStageIsNotPresent() { unit.filter(request, response); verifyZeroInteractions(logbook); } diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookServerFilterTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookServerFilterTest.java index 3696a50a2..5d57385f0 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookServerFilterTest.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/LogbookServerFilterTest.java @@ -11,7 +11,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verifyZeroInteractions; -public final class LogbookServerFilterTest { +final class LogbookServerFilterTest { private final ContainerRequestContext request = mock(ContainerRequestContext.class); private final ContainerResponseContext response = mock(ContainerResponseContext.class); @@ -20,7 +20,7 @@ public final class LogbookServerFilterTest { private final ContainerResponseFilter unit = new LogbookServerFilter(logbook); @Test - public void filterShouldDoNothingIfCorrelatorIsNotPresent() throws IOException { + void filterShouldDoNothingIfCorrelatorIsNotPresent() throws IOException { unit.filter(request, response); verifyZeroInteractions(logbook); } diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/TeeOutputStreamTest.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/TeeOutputStreamTest.java index 890f9e810..e5d857a9a 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/TeeOutputStreamTest.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/TeeOutputStreamTest.java @@ -13,14 +13,14 @@ class TeeOutputStreamTest { private final TeeOutputStream unit = new TeeOutputStream(output); @Test - public void shouldWriteByte() throws IOException { + void shouldWriteByte() throws IOException { unit.write(17); assertArrayEquals(unit.toByteArray(), output.toByteArray()); } @Test - public void shouldWriteBytesWithoutOffsets() throws IOException { + void shouldWriteBytesWithoutOffsets() throws IOException { unit.write(new byte[] {17}); assertArrayEquals(unit.toByteArray(), output.toByteArray()); @@ -28,7 +28,7 @@ public void shouldWriteBytesWithoutOffsets() throws IOException { } @Test - public void shouldWriteBytesWithOffsets() throws IOException { + void shouldWriteBytesWithOffsets() throws IOException { unit.write(new byte[] {17}, 0, 1); assertArrayEquals(unit.toByteArray(), output.toByteArray()); diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/UnitTestSetupException.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/UnitTestSetupException.java deleted file mode 100644 index 131b82de0..000000000 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/UnitTestSetupException.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.zalando.logbook.jaxrs; - -public class UnitTestSetupException extends RuntimeException { - - public UnitTestSetupException(Exception ex) { - super(ex); - } -} diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java new file mode 100644 index 000000000..d07ea95e2 --- /dev/null +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java @@ -0,0 +1,44 @@ +package org.zalando.logbook.jaxrs; + +import org.zalando.logbook.Correlation; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Precorrelation; +import org.zalando.logbook.Sink; +import org.zalando.logbook.Strategy; + +import java.io.IOException; + +import static java.util.Collections.singletonList; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasToString; + +final class WithBodyStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + return request.withBody().withBody(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request, + final Sink sink) throws IOException { + + request.getBodyAsString(); + sink.write(precorrelation, request); + } + + @Override + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { + return response.withBody().withBody(); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, + final Sink sink) throws IOException { + + response.getBodyAsString(); + sink.write(correlation, request, response); + } + +} diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java new file mode 100644 index 000000000..e31a1af94 --- /dev/null +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java @@ -0,0 +1,34 @@ +package org.zalando.logbook.jaxrs; + +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; +import org.zalando.logbook.Strategy; + +import java.io.IOException; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.emptyArray; +import static org.hamcrest.Matchers.emptyString; +import static org.hamcrest.Matchers.is; + +final class WithoutBodyStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + request.getBodyAsString(); + request.withoutBody(); + assertThat(request.getBodyAsString(), is(emptyString())); + request.withBody(); + return request.withoutBody(); + } + + @Override + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { + response.getBodyAsString(); + response.withoutBody(); + assertThat(response.getBodyAsString(), is(emptyString())); + response.withBody(); + return response.withoutBody(); + } + +} diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/testing/support/TestModel.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/testing/support/TestModel.java index fd0bb57c4..0bf8b3b5f 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/testing/support/TestModel.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/testing/support/TestModel.java @@ -9,7 +9,7 @@ public String getProperty1() { return property1; } - public TestModel setProperty1(String property1) { + public TestModel setProperty1(final String property1) { this.property1 = property1; return this; } @@ -18,7 +18,7 @@ public String getProperty2() { return property2; } - public TestModel setProperty2(String property2) { + public TestModel setProperty2(final String property2) { this.property2 = property2; return this; } diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/GzipInterceptor.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/GzipInterceptor.java index e492b9415..699562e9b 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/GzipInterceptor.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/GzipInterceptor.java @@ -35,7 +35,7 @@ public Response intercept(final Chain chain) throws IOException { return response; } - private boolean isContentEncodingGzip(Response response) { + private boolean isContentEncodingGzip(final Response response) { return "gzip".equalsIgnoreCase(response.header("Content-Encoding")); } diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java index 53c676295..a9615f5a9 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/LocalRequest.java @@ -21,9 +21,9 @@ final class LocalRequest implements HttpRequest { private Request request; - private byte[] body; + @Nullable private byte[] body; - public LocalRequest(final Request request) { + LocalRequest(final Request request) { this.request = request; } diff --git a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java index f90993373..b3c64681d 100644 --- a/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java +++ b/logbook-okhttp/src/main/java/org/zalando/logbook/okhttp/RemoteResponse.java @@ -6,6 +6,7 @@ import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Origin; +import javax.annotation.Nullable; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; @@ -20,9 +21,10 @@ final class RemoteResponse implements HttpResponse { private Response response; + @Nullable private byte[] body; - public RemoteResponse(final Response response) { + RemoteResponse(final Response response) { this.response = response; } @@ -88,7 +90,7 @@ public RemoteResponse withoutBody() { return this; } - public Response toResponse() { + Response toResponse() { return response; } diff --git a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LocalRequestTest.java b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LocalRequestTest.java index 278342e25..99f40970f 100644 --- a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LocalRequestTest.java +++ b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LocalRequestTest.java @@ -7,7 +7,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public final class LocalRequestTest { +final class LocalRequestTest { private LocalRequest unit(final Request request) { return new LocalRequest(request); diff --git a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java index 8c5305b81..cfb79faf3 100644 --- a/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java +++ b/logbook-okhttp/src/test/java/org/zalando/logbook/okhttp/LogbookInterceptorTest.java @@ -12,12 +12,17 @@ import org.zalando.logbook.DefaultHttpLogFormatter; import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; +import org.zalando.logbook.Strategy; import java.io.IOException; +import java.net.HttpURLConnection; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.GET; +import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.HEAD; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.POST; import static com.github.restdriver.clientdriver.RestClientDriver.giveEmptyResponse; import static com.github.restdriver.clientdriver.RestClientDriver.giveResponse; @@ -41,10 +46,20 @@ final class LogbookInterceptorTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() - .sink(new DefaultSink( - new DefaultHttpLogFormatter(), - writer - )) + .strategy(new Strategy() { + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + request.getBody(); + return request.withBody().withBody(); + } + + @Override + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { + response.getBody(); + return response.withBody().withBody(); + } + }) + .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); private final OkHttpClient client = new OkHttpClient.Builder() @@ -90,7 +105,6 @@ void shouldLogRequestWithBody() throws IOException { assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureRequest() throws IOException { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(writer).write(any(Precorrelation.class), captor.capture()); @@ -108,6 +122,38 @@ void shouldNotLogRequestIfInactive() throws IOException { verify(writer, never()).write(any(Precorrelation.class), any()); } + @Test + void shouldLogResponseForNotModified() throws IOException { + driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse().withStatus( + HttpURLConnection.HTTP_NOT_MODIFIED)); + + sendAndReceive(); + + final String message = captureResponse(); + + assertThat(message, startsWith("Incoming Response:")); + assertThat(message, containsString("HTTP/1.1 304 Not Modified")); + assertThat(message, not(containsStringIgnoringCase("Content-Type"))); + assertThat(message, not(containsString("Hello, world!"))); + } + + @Test + void shouldLogResponseForHeadRequest() throws IOException { + driver.addExpectation(onRequestTo("/").withMethod(HEAD), giveEmptyResponse()); + + client.newCall(new Request.Builder() + .method("HEAD", null) + .url(driver.getBaseUrl()) + .build()).execute(); + + final String message = captureResponse(); + + assertThat(message, startsWith("Incoming Response:")); + assertThat(message, containsString("HTTP/1.1 204 No Content")); + assertThat(message, not(containsStringIgnoringCase("Content-Type"))); + assertThat(message, not(containsString("Hello, world!"))); + } + @Test void shouldLogResponseWithoutBody() throws IOException { driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); @@ -141,7 +187,6 @@ void shouldLogResponseWithBody() throws IOException { assertThat(message, containsString("Hello, world!")); } - @SuppressWarnings("unchecked") private String captureResponse() throws IOException { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); verify(writer).write(any(Correlation.class), captor.capture()); diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java index 2a0d3dc54..7aab4d85c 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/GzipInterceptor.java @@ -38,7 +38,7 @@ public Response intercept(final Chain chain) throws IOException { return response; } - private boolean isContentEncodingGzip(Response response) { + private boolean isContentEncodingGzip(final Response response) { return "gzip".equalsIgnoreCase(response.header("Content-Encoding")); } diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java index f96a6921e..e8889a121 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/LocalRequest.java @@ -23,7 +23,7 @@ final class LocalRequest implements HttpRequest { private Request request; private byte[] body; - public LocalRequest(final Request request) { + LocalRequest(final Request request) { this.request = request; } diff --git a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java index 6f5706680..f86610b59 100644 --- a/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java +++ b/logbook-okhttp2/src/main/java/org/zalando/logbook/okhttp2/RemoteResponse.java @@ -22,7 +22,7 @@ final class RemoteResponse implements HttpResponse { private Response response; private byte[] body; - public RemoteResponse(final Response response) { + RemoteResponse(final Response response) { this.response = response; } @@ -88,7 +88,7 @@ public RemoteResponse withoutBody() { return this; } - public Response toResponse() { + Response toResponse() { return response; } diff --git a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java index 21e58b5a8..7abfbe93b 100644 --- a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java +++ b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/GzipInterceptorTest.java @@ -40,7 +40,7 @@ final class GzipInterceptorTest { private final ClientDriver driver = new ClientDriverFactory().createClientDriver(); private final OkHttpClient client; - public GzipInterceptorTest() { + GzipInterceptorTest() { client = new OkHttpClient(); client.networkInterceptors().add(new LogbookInterceptor(logbook)); client.networkInterceptors().add(new GzipInterceptor()); diff --git a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java index 21723da42..e5211d47c 100644 --- a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java +++ b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java @@ -8,7 +8,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public final class LocalRequestTest { +final class LocalRequestTest { private LocalRequest unit(final Request request) { return new LocalRequest(request); diff --git a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java index 3673803e5..48114cf78 100644 --- a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java +++ b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LogbookInterceptorTest.java @@ -8,14 +8,17 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogWriter; +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; +import org.zalando.logbook.Strategy; import java.io.IOException; -import java.net.HttpURLConnection; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.GET; import static com.github.restdriver.clientdriver.ClientDriverRequest.Method.HEAD; @@ -42,21 +45,33 @@ final class LogbookInterceptorTest { private final HttpLogWriter writer = mock(HttpLogWriter.class); private final Logbook logbook = Logbook.builder() + .strategy(new Strategy() { + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + request.getBody(); + return request.withBody().withBody(); + } + + @Override + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { + response.getBody(); + return response.withBody().withBody(); + } + }) .sink(new DefaultSink(new DefaultHttpLogFormatter(), writer)) .build(); - private final OkHttpClient client; + private final OkHttpClient client = new OkHttpClient(); private final ClientDriver driver = new ClientDriverFactory().createClientDriver(); - public LogbookInterceptorTest() { - client = new OkHttpClient(); + LogbookInterceptorTest() { client.networkInterceptors().add(new LogbookInterceptor(logbook)); } @BeforeEach void defaultBehaviour() { - when(writer.isActive()).thenReturn(true); + when(writer.isActive()).thenCallRealMethod(); } @Test @@ -110,8 +125,7 @@ void shouldNotLogRequestIfInactive() throws IOException { @Test void shouldLogResponseForNotModified() throws IOException { - driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse().withStatus( - HttpURLConnection.HTTP_NOT_MODIFIED)); + driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse().withStatus(304)); sendAndReceive(); @@ -142,14 +156,14 @@ void shouldLogResponseForHeadRequest() throws IOException { @Test void shouldLogResponseWithoutBody() throws IOException { - driver.addExpectation(onRequestTo("/").withMethod(GET), giveEmptyResponse()); + driver.addExpectation(onRequestTo("/").withMethod(GET), giveResponse("", "text/plain")); sendAndReceive(); final String message = captureResponse(); assertThat(message, startsWith("Incoming Response:")); - assertThat(message, containsString("HTTP/1.1 204 No Content")); + assertThat(message, containsString("HTTP/1.1 200 OK")); assertThat(message, not(containsStringIgnoringCase("Content-Type"))); assertThat(message, not(containsString("Hello, world!"))); } @@ -175,7 +189,7 @@ void shouldLogResponseWithBody() throws IOException { private String captureResponse() throws IOException { final ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - verify(writer).write(any(), captor.capture()); + verify(writer).write(any(Correlation.class), captor.capture()); return captor.getValue(); } @@ -187,7 +201,7 @@ void shouldNotLogResponseIfInactive() throws IOException { sendAndReceive(); - verify(writer, never()).write(any(), any()); + verify(writer, never()).write(any(Correlation.class), any()); } private void sendAndReceive() throws IOException { diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java index d99590615..d96ef486d 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java @@ -1,5 +1,6 @@ package org.zalando.logbook.servlet; +import org.zalando.logbook.Headers; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Origin; @@ -12,6 +13,7 @@ import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.Charset; +import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; @@ -42,13 +44,13 @@ public String getProtocolVersion() { @Override public Map> getHeaders() { - final HeadersBuilder builder = new HeadersBuilder(); + final Map> headers = Headers.empty(); for (final String header : getHeaderNames()) { - builder.put(header, getHeaders(header)); + headers.put(header, new ArrayList<>(getHeaders(header))); } - return builder.build(); + return headers; } @Override @@ -118,7 +120,7 @@ PrintWriter getWriter(final Supplier charset) { } byte[] getBytes() { - if (bytes == null || bytes.length != branch.size()) { + if (bytes == null) { bytes = branch.toByteArray(); } return bytes; diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java index 6771d5bf3..937f24c4c 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java @@ -1,11 +1,11 @@ package org.zalando.logbook.servlet; import lombok.SneakyThrows; +import org.zalando.logbook.Headers; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Origin; import javax.activation.MimeType; -import javax.annotation.Nullable; import javax.servlet.ServletInputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; @@ -72,15 +72,17 @@ public String getQuery() { @Override public Map> getHeaders() { - final HeadersBuilder builder = new HeadersBuilder(); + final Map> headers = Headers.empty(); final Enumeration names = getHeaderNames(); while (names.hasMoreElements()) { final String name = names.nextElement(); - builder.put(name, list(getHeaders(name))); + + headers.put(name, list(getHeaders(name))); } - return builder.build(); + // TODO immutable? + return headers; } @Override diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java index 2b870a749..4b5e8b2d3 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java @@ -13,6 +13,7 @@ import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Strategy; import javax.servlet.DispatcherType; import java.io.IOException; @@ -40,7 +41,7 @@ /** * Verifies that {@link LogbookFilter} handles {@link DispatcherType#ASYNC} correctly. */ -public final class AsyncDispatchTest { +final class AsyncDispatchTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -48,12 +49,25 @@ public final class AsyncDispatchTest { private final MockMvc mvc = MockMvcBuilders .standaloneSetup(new ExampleController()) .addFilter(new LogbookFilter(Logbook.builder() + .strategy(new Strategy() { + @Override + public HttpRequest process(final HttpRequest request) throws IOException { + request.getBody(); + return request.withBody().withBody(); + } + + @Override + public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { + response.getBody(); + return response.withBody().withBody(); + } + }) .sink(new DefaultSink(formatter, writer)) .build())) .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ByteStreamsTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ByteStreamsTest.java index 40e5a4824..579ba4a7d 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ByteStreamsTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ByteStreamsTest.java @@ -10,7 +10,7 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; -public final class ByteStreamsTest { +final class ByteStreamsTest { @Test void shouldCollectStreamToByteArray() throws IOException { diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java index f8adf37cc..a65f014fa 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java @@ -33,7 +33,7 @@ /** * Verifies that {@link LogbookFilter} handles {@link DispatcherType#ERROR} correctly. */ -public final class ErrorDispatchTest { +final class ErrorDispatchTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -46,7 +46,7 @@ public final class ErrorDispatchTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java index 6d6c68bc8..83562f538 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java @@ -87,7 +87,7 @@ public void stream(final HttpServletRequest request, final HttpServletResponse r @RequestMapping(value = "/reader", produces = MediaType.TEXT_PLAIN_VALUE) public void reader(final HttpServletRequest request, final HttpServletResponse response) throws IOException { - try (PrintWriter writer = response.getWriter()) { + try (final PrintWriter writer = response.getWriter()) { copy(request.getReader(), writer); } } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java index e35fd29c6..f2e85280c 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/FormattingTest.java @@ -40,7 +40,7 @@ /** * Verifies that {@link LogbookFilter} delegates to {@link HttpLogFormatter} correctly. */ -public final class FormattingTest { +final class FormattingTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -53,7 +53,7 @@ public final class FormattingTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java index def61d4dd..8cdf40fbe 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ForwardingHttpLogFormatter.java @@ -13,11 +13,11 @@ class ForwardingHttpLogFormatter implements HttpLogFormatter { private final HttpLogFormatter formatter; - protected ForwardingHttpLogFormatter(final HttpLogFormatter formatter) { + ForwardingHttpLogFormatter(final HttpLogFormatter formatter) { this.formatter = formatter; } - protected HttpLogFormatter delegate() { + private HttpLogFormatter delegate() { return formatter; } @@ -27,7 +27,7 @@ public String format(final Precorrelation precorrelation, final HttpRequest requ } @Override - public String format(final Correlation correlation, HttpResponse response) + public String format(final Correlation correlation, final HttpResponse response) throws IOException { return delegate().format(correlation, response); } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/HttpSupportTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/HttpSupportTest.java index 7c12f9a51..273088b1b 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/HttpSupportTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/HttpSupportTest.java @@ -19,7 +19,7 @@ /** * Verifies that {@link LogbookFilter} rejects non-HTTP requests/responses. */ -public final class HttpSupportTest { +final class HttpSupportTest { private final Logbook logbook = mock(Logbook.class); private final LogbookFilter unit = new LogbookFilter(logbook); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java index be22780be..90c91c7eb 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LocalResponseTest.java @@ -19,13 +19,13 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import static org.mockito.Mockito.when; -public class LocalResponseTest { +class LocalResponseTest { private HttpServletResponse mock; private LocalResponse unit; @BeforeEach - public void setUp() throws IOException { + void setUp() throws IOException { mock = mock(HttpServletResponse.class); when(mock.getOutputStream()).thenReturn(new ServletOutputStream() { @Override diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java index 5926e0792..27bb60f51 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java @@ -7,7 +7,7 @@ import static org.mockito.Mockito.mock; -public final class LogbookFilterTest { +final class LogbookFilterTest { @Test void shouldCreateLogbookFilter() { diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/Message.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/Message.java index 1dbaa9109..5b9ea32f9 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/Message.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/Message.java @@ -1,6 +1,6 @@ package org.zalando.logbook.servlet; -public class Message { +class Message { private String value; diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java index 4879c3bc2..ccbc1d185 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java @@ -37,7 +37,7 @@ /** * Verifies that {@link LogbookFilter} handles complex security setups correctly. */ -public final class MultiFilterSecurityTest { +final class MultiFilterSecurityTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new JsonHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -58,7 +58,7 @@ public final class MultiFilterSecurityTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java index 3410541dc..bbb5538ab 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterTest.java @@ -35,7 +35,7 @@ /** * Verifies that {@link LogbookFilter} handles cases correctly when multiple instances are running in the same chain. */ -public final class MultiFilterTest { +final class MultiFilterTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -54,7 +54,7 @@ public final class MultiFilterTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/NullOutputStreamTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/NullOutputStreamTest.java index 3c80e751d..43053aa97 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/NullOutputStreamTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/NullOutputStreamTest.java @@ -4,7 +4,7 @@ import static org.zalando.logbook.servlet.NullOutputStream.NULL; -public final class NullOutputStreamTest { +final class NullOutputStreamTest { @Test void shouldIgnoreByte() throws Exception { diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java index 9db834dd2..1470c92ed 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SkipTest.java @@ -28,7 +28,7 @@ /** * Verifies that {@link LogbookFilter} handles {@link DispatcherType#ASYNC} correctly. */ -public final class SkipTest { +final class SkipTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -41,7 +41,7 @@ public final class SkipTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(false); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java index 4efe6d959..40f9b89ae 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/TeeTest.java @@ -25,7 +25,7 @@ * Verifies that {@link LogbookFilter} handles the copying of streams in {@link RemoteRequest} and {@link LocalResponse} * correctly. */ -public final class TeeTest { +final class TeeTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -38,7 +38,7 @@ public final class TeeTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java index c0d848e1d..3b4343dc5 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/WritingTest.java @@ -34,7 +34,7 @@ */ @NotThreadSafe @RestoreSystemProperties -public final class WritingTest { +final class WritingTest { private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); private final HttpLogWriter writer = mock(HttpLogWriter.class); @@ -47,7 +47,7 @@ public final class WritingTest { .build(); @BeforeEach - public void setUp() { + void setUp() { reset(formatter, writer); when(writer.isActive()).thenReturn(true); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/junit/SystemPropertiesExtension.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/junit/SystemPropertiesExtension.java index 35b2c4164..560b7c64b 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/junit/SystemPropertiesExtension.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/junit/SystemPropertiesExtension.java @@ -7,7 +7,7 @@ import java.util.Properties; -public final class SystemPropertiesExtension +final class SystemPropertiesExtension implements Extension, BeforeEachCallback, AfterEachCallback { private final Properties original = new Properties(); diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/LogbookTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/LogbookTest.java index a7d02301c..d70ca06cd 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/LogbookTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/LogbookTest.java @@ -28,7 +28,7 @@ @WebAppConfiguration @Import(Void.class) @ExtendWith(SpringExtension.class) -public @interface LogbookTest { +@interface LogbookTest { @AliasFor(annotation = ActiveProfiles.class, attribute = "profiles") String[] profiles() default {}; diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java index 76616eca6..602b21cee 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/WriteLevelTest.java @@ -29,7 +29,7 @@ void setUp() { } @Test - public void shouldUseConfiguredLevel() throws IOException { + void shouldUseConfiguredLevel() throws IOException { logbook.process(MockHttpRequest.create()).write(); verify(logger).warn(anyString()); diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java b/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java index 63eb6596b..43a596c2e 100644 --- a/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java +++ b/logbook-test/src/main/java/org/zalando/logbook/MockHeaders.java @@ -2,8 +2,10 @@ import org.apiguardian.api.API; +import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Optional; import static org.apiguardian.api.API.Status.MAINTAINED; @@ -28,10 +30,17 @@ public static Map> of(final String k1, final String v1, fin } private static Map> buildHeaders(final String... x) { - final HttpMessage.HeadersBuilder builder = new HttpMessage.HeadersBuilder(); + final Map> headers = Headers.empty(); + for (int i = 0; i < x.length; i += 2) { - builder.put(x[i], x[i + 1]); + final String value = x[i + 1]; + headers.compute(x[i], (key, before) -> { + final List after = Optional.ofNullable(before).orElseGet(ArrayList::new); + after.add(value); + return after; + }); } - return builder.build(); + + return headers; } } diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java b/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java index 3cf0e53e4..4ae108d47 100644 --- a/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java +++ b/logbook-test/src/main/java/org/zalando/logbook/MockHttpRequest.java @@ -52,7 +52,8 @@ public HttpRequest withBody() { @Override public HttpRequest withoutBody() { - return withBodyAsString(""); + bodyAsString = ""; + return this; } } diff --git a/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java b/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java index dd208baef..f6b3b0884 100644 --- a/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java +++ b/logbook-test/src/main/java/org/zalando/logbook/MockHttpResponse.java @@ -45,7 +45,8 @@ public HttpResponse withBody() { @Override public HttpResponse withoutBody() { - return withBodyAsString(""); + bodyAsString = ""; + return this; } } diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockHeadersTest.java b/logbook-test/src/test/java/org/zalando/logbook/MockHeadersTest.java index 9b777e16a..d11fcfa61 100644 --- a/logbook-test/src/test/java/org/zalando/logbook/MockHeadersTest.java +++ b/logbook-test/src/test/java/org/zalando/logbook/MockHeadersTest.java @@ -8,7 +8,7 @@ import static java.util.Collections.singletonList; import static org.junit.jupiter.api.Assertions.assertEquals; -public class MockHeadersTest { +class MockHeadersTest { @Test void satisfyCoverage() { diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java b/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java index ad19c722b..ece7461b3 100644 --- a/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java +++ b/logbook-test/src/test/java/org/zalando/logbook/MockHttpMessageTester.java @@ -15,7 +15,7 @@ import static org.zalando.logbook.Origin.LOCAL; import static org.zalando.logbook.Origin.REMOTE; -public interface MockHttpMessageTester { +interface MockHttpMessageTester { default void verifyRequest(final HttpRequest unit) throws IOException { assertThat(unit.getProtocolVersion(), is("HTTP/1.1")); diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockHttpRequestTest.java b/logbook-test/src/test/java/org/zalando/logbook/MockHttpRequestTest.java index d3b73cc40..c613c8405 100644 --- a/logbook-test/src/test/java/org/zalando/logbook/MockHttpRequestTest.java +++ b/logbook-test/src/test/java/org/zalando/logbook/MockHttpRequestTest.java @@ -14,7 +14,7 @@ import static org.zalando.logbook.MockHeaders.of; import static org.zalando.logbook.Origin.LOCAL; -public final class MockHttpRequestTest implements MockHttpMessageTester { +final class MockHttpRequestTest implements MockHttpMessageTester { private final MockHttpRequest unit = MockHttpRequest.create(); @@ -43,4 +43,16 @@ void shouldSupportWith() { assertWith(unit, MockHttpRequest::withBodyAsString, "Hello", throwingFunction(HttpRequest::getBodyAsString)); } + @Test + void shouldSupportWithBody() throws IOException { + assertThat(unit.withBodyAsString("Hello").withBody().getBodyAsString(), is("Hello")); + } + + @Test + void shouldSupportWithoutBody() { + unit.withoutBody(); + + assertThat(unit.getBodyAsString(), is(emptyString())); + } + } diff --git a/logbook-test/src/test/java/org/zalando/logbook/MockHttpResponseTest.java b/logbook-test/src/test/java/org/zalando/logbook/MockHttpResponseTest.java index ba5acbd6f..030050539 100644 --- a/logbook-test/src/test/java/org/zalando/logbook/MockHttpResponseTest.java +++ b/logbook-test/src/test/java/org/zalando/logbook/MockHttpResponseTest.java @@ -13,7 +13,7 @@ import static org.zalando.logbook.MockHeaders.of; import static org.zalando.logbook.Origin.REMOTE; -public final class MockHttpResponseTest implements MockHttpMessageTester { +final class MockHttpResponseTest implements MockHttpMessageTester { private final MockHttpResponse unit = MockHttpResponse.create(); @@ -36,4 +36,16 @@ void shouldSupportWith() { assertWith(unit, MockHttpResponse::withBodyAsString, "Hello", throwingFunction(HttpResponse::getBodyAsString)); } + @Test + void shouldSupportWithBody() throws IOException { + assertThat(unit.withBodyAsString("Hello").withBody().getBodyAsString(), is("Hello")); + } + + @Test + void shouldSupportWithoutBody() { + unit.withoutBody(); + + assertThat(unit.getBodyAsString(), is(emptyString())); + } + } From 9b143677035909e05688cda8da574116bdbad268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Fri, 22 Feb 2019 21:33:44 +0100 Subject: [PATCH 06/13] Re-implemented servlet integration --- README.md | 101 ++++++++++-------- .../java/org/zalando/logbook/HttpMessage.java | 5 - .../org/zalando/logbook/LogbookFactory.java | 1 - .../org/zalando/logbook/CorrelationTest.java | 2 +- .../org/zalando/logbook/StrategyTest.java | 1 - .../logbook/jaxrs/WithBodyStrategy.java | 4 - .../logbook/jaxrs/WithoutBodyStrategy.java | 1 - .../logbook/okhttp2/LocalRequestTest.java | 1 - .../servlet/AbstractLogbookFilter.java | 92 ++++++++++++++++ .../zalando/logbook/servlet/Attributes.java | 13 --- .../zalando/logbook/servlet/HttpFilter.java | 42 -------- .../logbook/servlet/LogbookFilter.java | 24 ++--- .../logbook/servlet/NormalStrategy.java | 73 ------------- .../logbook/servlet/SecureLogbookFilter.java | 46 ++++++++ .../logbook/servlet/SecurityStrategy.java | 55 ---------- .../org/zalando/logbook/servlet/Strategy.java | 35 ------ .../logbook/servlet/ErrorDispatchTest.java | 89 --------------- .../logbook/servlet/LogbookFilterTest.java | 8 +- .../servlet/MultiFilterSecurityTest.java | 8 +- .../logbook/servlet/SecurityFilter.java | 31 ++++-- .../logbook/servlet/SpyableFilter.java | 26 +++-- .../spring/LogbookAutoConfiguration.java | 22 ++-- .../zalando/logbook/spring/FilterTest.java | 6 +- .../logbook/spring/FormatStyleCurlTest.java | 1 - .../spring/FormatStyleDefaultTest.java | 1 - .../logbook/spring/FormatStyleHttpTest.java | 1 - .../logbook/spring/SecurityFilterTest.java | 12 +-- 27 files changed, 272 insertions(+), 429 deletions(-) create mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java delete mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/Attributes.java delete mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java delete mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java create mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java delete mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java delete mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java delete mode 100644 logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java diff --git a/README.md b/README.md index e56c02aba..6205cf286 100644 --- a/README.md +++ b/README.md @@ -121,15 +121,15 @@ or create a customized version using the `LogbookBuilder`: ```java Logbook logbook = Logbook.builder() .condition(new CustomCondition()) - .rawRequestFilter(new CustomRawRequestFilter()) - .rawResponseFilter(new CustomRawResponseFilter()) .queryFilter(new CustomQueryFilter()) .headerFilter(new CustomHeaderFilter()) .bodyFilter(new CustomBodyFilter()) .requestFilter(new CustomRequestFilter()) .responseFilter(new CustomResponseFilter()) - .formatter(new CustomHttpLogFormatter()) - .writer(new CustomHttpLogWriter()) + .sink(new DefaultSink( + new CustomHttpLogFormatter(), + new CustomHttpLogWriter() + )) .build(); ``` @@ -180,16 +180,15 @@ Logbook supports different types of filters: | `ResponseFilter` | `HttpResponse` | response | n/a | `QueryFilter`, `HeaderFilter` and `BodyFilter` are relatively high-level and should cover all needs in ~90% of all -cases. For more complicated setups one should fallback to the low-level variants, i.e. `RawRequestFilter` and -`RawResponseFilter` as well as `RequestFilter` and `ResponseFilter` respectively (in conjunction with -`ForwardingHttpRequest`/`ForwardingHttpResponse` and `ForwardingHttpRequest`/`ForwardingHttpResponse`). +cases. For more complicated setups one should fallback to the low-level variants, i.e. `RequestFilter` and `ResponseFilter` +respectively (in conjunction with `ForwardingHttpRequest`/`ForwardingHttpResponse`). You can configure filters like this: ```java Logbook logbook = Logbook.builder() - .rawRequestFilter(replaceBody(contentType("audio/*"), "mmh mmh mmh mmh")) - .rawResponseFilter(replaceBody(contentType("*/*-stream"), "It just keeps going and going...")) + .requestFilter(replaceBody(contentType("audio/*"), "mmh mmh mmh mmh")) + .responseFilter(replaceBody(contentType("*/*-stream"), "It just keeps going and going...")) .queryFilter(accessToken()) .queryFilter(replaceQuery("password", "")) .headerFilter(authorization()) @@ -341,9 +340,12 @@ By default, requests and responses are logged with an *slf4j* logger that uses t ```java Logbook logbook = Logbook.builder() - .writer(new DefaultHttpLogWriter( - LoggerFactory.getLogger("http.wire-log"), - Level.DEBUG)) + .sink(new DefaultSink( + new DefaultHttpFormatter(), + new DefaultHttpLogWriter( + LoggerFactory.getLogger("http.wire-log"), + Level.DEBUG) + )) .build(); ``` @@ -353,7 +355,10 @@ An alternative implementation is to log requests and responses to a `PrintStream ```java Logbook logbook = Logbook.builder() - .writer(new StreamHttpLogWriter(System.err)) + .sink(new DefaultSink( + new DefaultHttpFormatter(), + new StreamHttpLogWriter(System.err) + )) .build(); ``` @@ -363,7 +368,10 @@ The `ChunkingHttpLogWriter` will split long messages into smaller chunks and wil ```java Logbook logbook = Logbook.builder() - .writer(new ChunkingHttpLogWriter(1000, new DefaultHttpLogWriter())) + .sink(new DefaultSink( + new DefaultHttpFormatter(), + new ChunkingHttpLogWriter(1000, new DefaultHttpLogWriter()) + )) .build(); ``` @@ -382,7 +390,6 @@ You’ll have to register the `LogbookFilter` as a `Filter` in your filter chain /* REQUEST ASYNC - ERROR ``` @@ -390,7 +397,7 @@ or programmatically, via the `ServletContext`: ```java context.addFilter("LogbookFilter", new LogbookFilter(logbook)) - .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC, ERROR), true, "/*"); + .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, "/*"); ``` The `LogbookFilter` will, by default, treat requests with a `application/x-www-form-urlencoded` body not different from @@ -417,12 +424,12 @@ Secure applications usually need a slightly different setup. You should generall You can easily achieve the former setup by placing the `LogbookFilter` after your security filter. The latter is a little bit more sophisticated. You’ll need two `LogbookFilter` instances — one before your security filter, and one after it: ```java -context.addFilter("unauthorizedLogbookFilter", new LogbookFilter(logbook, Strategy.SECURITY)) - .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC, ERROR), true, "/*"); +context.addFilter("SecureLogbookFilter", new SecureLogbookFilter(logbook)) + .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, "/*"); context.addFilter("securityFilter", new SecurityFilter()) .addMappingForUrlPatterns(EnumSet.of(REQUEST), true, "/*"); -context.addFilter("authorizedLogbookFilter", new LogbookFilter(logbook)) - .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC, ERROR), true, "/*"); +context.addFilter("LogbookFilter", new LogbookFilter(logbook)) + .addMappingForUrlPatterns(EnumSet.of(REQUEST, ASYNC), true, "/*"); ``` The first logbook filter will log unauthorized requests **only**. The second filter will log authorized requests, as always. @@ -511,21 +518,19 @@ Logbook comes with a convenient auto configuration for Spring Boot users. It set - HTTP-/JSON-style formatter - Logging writer -| Type | Name | Default | -|-----------------------------|-----------------------------|---------------------------------------------------------------------------| -| `FilterRegistrationBean` | `unauthorizedLogbookFilter` | Based on `LogbookFilter` | -| `FilterRegistrationBean` | `authorizedLogbookFilter` | Based on `LogbookFilter` | -| `Logbook` | | Based on condition, filters, formatter and writer | -| `Predicate` | `requestCondition` | No filter; is later combined with `logbook.exclude` and `logbook.exclude` | -| `RawRequestFilter` | | `RawRequestFilters.defaultValue()` | -| `RawResponseFilter` | | `RawResponseFilters.defaultValue()` | -| `HeaderFilter` | | Based on `logbook.obfuscate.headers` | -| `QueryFilter` | | Based on `logbook.obfuscate.parameters` | -| `BodyFilter` | | `BodyFilters.defaultValue()` | -| `RequestFilter` | | `RequestFilter.none()` | -| `ResponseFilter` | | `ResponseFilter.none()` | -| `HttpLogFormatter` | | `JsonHttpLogFormatter` | -| `HttpLogWriter` | | `DefaultHttpLogWriter` | +| Type | Name | Default | +|-----------------------------|-----------------------|---------------------------------------------------------------------------| +| `FilterRegistrationBean` | `secureLogbookFilter` | Based on `LogbookFilter` | +| `FilterRegistrationBean` | `logbookFilter` | Based on `LogbookFilter` | +| `Logbook` | | Based on condition, filters, formatter and writer | +| `Predicate` | `requestCondition` | No filter; is later combined with `logbook.exclude` and `logbook.exclude` | +| `HeaderFilter` | | Based on `logbook.obfuscate.headers` | +| `QueryFilter` | | Based on `logbook.obfuscate.parameters` | +| `BodyFilter` | | `BodyFilters.defaultValue()` | +| `RequestFilter` | | `RequestFilter.none()` | +| `ResponseFilter` | | `ResponseFilter.none()` | +| `HttpLogFormatter` | | `JsonHttpLogFormatter` | +| `HttpLogWriter` | | `DefaultHttpLogWriter` | Multiple filters are merged into one. @@ -533,18 +538,19 @@ Multiple filters are merged into one. 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(s)`](#servlet) | `true` | -| `logbook.format.style` | [Formatting style](#formatting) (`http`, `json`, `curl` or `splunk`) | `json` | -| `logbook.obfuscate.headers` | List of header names that need obfuscation | `[Authorization]` | -| `logbook.obfuscate.parameters` | List of parameter names that need obfuscation | `[access_token]` | -| `logbook.write.category` | Changes the category of the [`DefaultHttpLogWriter`](#logger) | `org.zalando.logbook.Logbook` | -| `logbook.write.level` | Changes the level of the [`DefaultHttpLogWriter`](#logger) | `TRACE` | -| `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`](#ser) | `true` | +| `logbook.secure-filter.enabled` | Enable the [`SecureLogbookFilter](#servlet) | `true` | +| `logbook.format.style` | [Formatting style](#formatting) (`http`, `json`, `curl` or `splunk`) | `json` | +| `logbook.obfuscate.headers` | List of header names that need obfuscation | `[Authorization]` | +| `logbook.obfuscate.parameters` | List of parameter names that need obfuscation | `[access_token]` | +| `logbook.write.category` | Changes the category of the [`DefaultHttpLogWriter`](#logger) | `org.zalando.logbook.Logbook` | +| `logbook.write.level` | Changes the level of the [`DefaultHttpLogWriter`](#logger) | `TRACE` | +| `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) | ##### Example configuration @@ -557,6 +563,7 @@ logbook: - /actuator/health - /api/admin/** filter.enabled: true + secure-filter.enabled: true format.style: http obfuscate: headers: diff --git a/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java b/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java index e80856a2a..9207aa094 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java +++ b/logbook-api/src/main/java/org/zalando/logbook/HttpMessage.java @@ -5,14 +5,9 @@ import javax.annotation.Nullable; import java.io.IOException; import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import static java.lang.String.CASE_INSENSITIVE_ORDER; import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) diff --git a/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java b/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java index 610efe721..863dad00e 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java +++ b/logbook-api/src/main/java/org/zalando/logbook/LogbookFactory.java @@ -3,7 +3,6 @@ import org.apiguardian.api.API; import javax.annotation.Nullable; - import java.util.function.Predicate; import static java.util.ServiceLoader.load; diff --git a/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java b/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java index 4f50c7b69..197b1b73c 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/CorrelationTest.java @@ -3,7 +3,7 @@ import org.junit.jupiter.api.Test; import org.mockito.invocation.InvocationOnMock; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertSame; import static org.mockito.Mockito.mock; class CorrelationTest { diff --git a/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java b/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java index 9381f24e1..0cc9de358 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java +++ b/logbook-api/src/test/java/org/zalando/logbook/StrategyTest.java @@ -5,7 +5,6 @@ import java.io.IOException; -import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verifyNoMoreInteractions; diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java index d07ea95e2..102fd2ef6 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithBodyStrategy.java @@ -9,10 +9,6 @@ import java.io.IOException; -import static java.util.Collections.singletonList; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasToString; - final class WithBodyStrategy implements Strategy { @Override diff --git a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java index e31a1af94..6687abc45 100644 --- a/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java +++ b/logbook-jaxrs/src/test/java/org/zalando/logbook/jaxrs/WithoutBodyStrategy.java @@ -7,7 +7,6 @@ import java.io.IOException; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.emptyArray; import static org.hamcrest.Matchers.emptyString; import static org.hamcrest.Matchers.is; diff --git a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java index e5211d47c..4e7595f34 100644 --- a/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java +++ b/logbook-okhttp2/src/test/java/org/zalando/logbook/okhttp2/LocalRequestTest.java @@ -1,7 +1,6 @@ package org.zalando.logbook.okhttp2; import com.squareup.okhttp.Request; - import org.junit.jupiter.api.Test; import static java.util.Optional.empty; diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java new file mode 100644 index 000000000..d7cdbfdca --- /dev/null +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java @@ -0,0 +1,92 @@ +package org.zalando.logbook.servlet; + +import org.zalando.logbook.HttpRequest; +import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseProcessingStage; +import org.zalando.logbook.RequestFilter; + +import javax.servlet.DispatcherType; +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.zalando.logbook.RequestFilters.replaceBody; + +abstract class AbstractLogbookFilter implements Filter { + + private static final String STAGE = ResponseProcessingStage.class.getName(); + + private final RequestFilter filter = replaceBody(message -> ""); + private final Logbook logbook; + + protected AbstractLogbookFilter(final Logbook logbook) { + this.logbook = logbook; + } + + @Override + public final void init(final FilterConfig filterConfig) { + // no initialization needed by default + } + + @Override + public final void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws ServletException, IOException { + + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new IllegalArgumentException(getClass().getSimpleName() + " only supports HTTP"); + } + + final HttpServletRequest httpRequest = (HttpServletRequest) request; + final HttpServletResponse httpResponse = (HttpServletResponse) response; + + doFilter(httpRequest, httpResponse, chain); + } + + protected abstract void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, + final FilterChain chain) throws ServletException, IOException; + + protected final ResponseProcessingStage logRequest(final HttpServletRequest httpRequest, + final HttpRequest request) throws IOException { + + if (isFirstRequest(httpRequest)) { + final ResponseProcessingStage stage = logbook.process(request).write(); + httpRequest.setAttribute(STAGE, stage); + return stage; + } else { + return (ResponseProcessingStage) httpRequest.getAttribute(STAGE); + } + } + + protected final void logResponse(final RemoteRequest request, final LocalResponse response, + final Logbook.ResponseWritingStage stage) throws IOException { + + if (isLastRequest(request)) { + response.getWriter().flush(); + stage.write(); + } + } + + protected final boolean isFirstRequest(final HttpServletRequest request) { + return request.getDispatcherType() != DispatcherType.ASYNC; + } + + protected final boolean isLastRequest(final HttpServletRequest request) { + return !request.isAsyncStarted(); + } + + protected final HttpRequest skipBody(final RemoteRequest request) { + return filter.filter(request); + } + + @Override + public final void destroy() { + // no deconstruction needed by default + } + +} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Attributes.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Attributes.java deleted file mode 100644 index 2a3520a48..000000000 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Attributes.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.zalando.logbook.servlet; - -import org.zalando.logbook.Logbook; - -final class Attributes { - - static final String CORRELATOR = Logbook.class.getName() + ".CORRELATOR"; - - private Attributes() { - - } - -} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java deleted file mode 100644 index 37359b644..000000000 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.zalando.logbook.servlet; - -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -interface HttpFilter extends Filter { - - @Override - default void init(final FilterConfig filterConfig) { - // no initialization needed by default - } - - @Override - default void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws ServletException, IOException { - - if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { - throw new IllegalArgumentException(getClass().getSimpleName() + " only supports HTTP"); - } - - final HttpServletRequest httpRequest = (HttpServletRequest) request; - final HttpServletResponse httpResponse = (HttpServletResponse) response; - - doFilter(httpRequest, httpResponse, chain); - } - - void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, - final FilterChain chain) throws ServletException, IOException; - - @Override - default void destroy() { - // no deconstruction needed by default - } - -} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java index 49999d03e..01bf116a0 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java @@ -2,6 +2,7 @@ import org.apiguardian.api.API; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseWritingStage; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -9,34 +10,31 @@ import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import static org.apiguardian.api.API.Status.MAINTAINED; import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public final class LogbookFilter implements HttpFilter { - - private final Logbook logbook; - private final Strategy strategy; +public final class LogbookFilter extends AbstractLogbookFilter { public LogbookFilter() { this(Logbook.create()); } public LogbookFilter(final Logbook logbook) { - this(logbook, Strategy.NORMAL); - } - - @API(status = MAINTAINED) - public LogbookFilter(final Logbook logbook, final Strategy strategy) { - this.logbook = logbook; - this.strategy = strategy; + super(logbook); } @Override public void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, final FilterChain chain) throws ServletException, IOException { - strategy.doFilter(logbook, httpRequest, httpResponse, chain); + final RemoteRequest request = new RemoteRequest(httpRequest); + final LocalResponse response = new LocalResponse(httpResponse, request.getProtocolVersion()); + + final ResponseWritingStage stage = logRequest(request, request).process(response); + + chain.doFilter(request, response); + + logResponse(request, response, stage); } } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java deleted file mode 100644 index f2fbd34c3..000000000 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/NormalStrategy.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.zalando.logbook.servlet; - -import org.zalando.logbook.HttpRequest; -import org.zalando.logbook.Logbook; -import org.zalando.logbook.Logbook.ResponseProcessingStage; -import org.zalando.logbook.Logbook.ResponseWritingStage; -import org.zalando.logbook.RequestFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE; -import static org.zalando.logbook.servlet.Attributes.CORRELATOR; - -final class NormalStrategy implements Strategy { - - private final RequestFilter filter; - - NormalStrategy(final RequestFilter filter) { - this.filter = filter; - } - - @Override - public void doFilter(final Logbook logbook, final HttpServletRequest httpRequest, - final HttpServletResponse httpResponse, final FilterChain chain) throws ServletException, IOException { - - final RemoteRequest request = new RemoteRequest(httpRequest); - final ResponseProcessingStage stage = logRequest(logbook, request); - - final String protocolVersion = request.getProtocolVersion(); - final LocalResponse response = new LocalResponse(httpResponse, protocolVersion); - - final ResponseWritingStage writingStage = stage.process(response); - - chain.doFilter(request, response); - logResponse(writingStage, request, response); - } - - private ResponseProcessingStage logRequest(final Logbook logbook, - final RemoteRequest request) throws IOException { - if (isFirstRequest(request)) { - final ResponseProcessingStage stage = logbook.process(skipBodyIfErrorDispatch(request)).write(); - request.setAttribute(CORRELATOR, stage); - return stage; - } else { - return readCorrelator(request); - } - } - - private HttpRequest skipBodyIfErrorDispatch(final RemoteRequest request) { - if (request.getAttribute(ERROR_EXCEPTION_TYPE) == null) { - return request; - } - return filter.filter(request); - } - - private ResponseProcessingStage readCorrelator(final RemoteRequest request) { - return (ResponseProcessingStage) request.getAttribute(CORRELATOR); - } - - private void logResponse(final ResponseWritingStage correlator, final RemoteRequest request, - final LocalResponse response) throws IOException { - - if (isLastRequest(request)) { - response.getWriter().flush(); - correlator.write(); - } - } - -} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java new file mode 100644 index 000000000..35bb0333f --- /dev/null +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java @@ -0,0 +1,46 @@ +package org.zalando.logbook.servlet; + +import org.apiguardian.api.API; +import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseWritingStage; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +import static org.apiguardian.api.API.Status.STABLE; + +@API(status = STABLE) +public final class SecureLogbookFilter extends AbstractLogbookFilter { + + public SecureLogbookFilter() { + this(Logbook.create()); + } + + public SecureLogbookFilter(final Logbook logbook) { + super(logbook); + } + + @Override + public void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, + final FilterChain chain) throws ServletException, IOException { + + final RemoteRequest request = new RemoteRequest(httpRequest); + final LocalResponse response = new LocalResponse(httpResponse, request.getProtocolVersion()); + + chain.doFilter(request, response); + + if (isUnauthorizedOrForbidden(response)) { + final ResponseWritingStage stage = logRequest(request, skipBody(request)).process(response); + logResponse(request, response, stage); + } + } + + private boolean isUnauthorizedOrForbidden(final HttpServletResponse response) { + final int status = response.getStatus(); + return status == 401 || status == 403; + } + +} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java deleted file mode 100644 index ad47e2401..000000000 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecurityStrategy.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.zalando.logbook.servlet; - -import org.zalando.logbook.Logbook; -import org.zalando.logbook.Logbook.ResponseProcessingStage; -import org.zalando.logbook.RequestFilter; - -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -import static org.zalando.logbook.servlet.Attributes.CORRELATOR; - -final class SecurityStrategy implements Strategy { - - private final RequestFilter filter; - - SecurityStrategy(final RequestFilter filter) { - this.filter = filter; - } - - @Override - public void doFilter(final Logbook logbook, final HttpServletRequest httpRequest, - final HttpServletResponse httpResponse, final FilterChain chain) throws ServletException, IOException { - - final RemoteRequest request = new RemoteRequest(httpRequest); - final String protocolVersion = request.getProtocolVersion(); - final LocalResponse response = new LocalResponse(httpResponse, protocolVersion); - - chain.doFilter(request, response); - - if (isUnauthorizedOrForbidden(response)) { - final ResponseProcessingStage stage; - - if (isFirstRequest(request)) { - stage = logbook.process(filter.filter(request)).write(); - } else { - stage = readCorrelator(request); - } - - stage.process(response).write(); - } - } - - private boolean isUnauthorizedOrForbidden(final HttpServletResponse response) { - final int status = response.getStatus(); - return status == 401 || status == 403; - } - - private ResponseProcessingStage readCorrelator(final RemoteRequest request) { - return (ResponseProcessingStage) request.getAttribute(CORRELATOR); - } - -} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java deleted file mode 100644 index c4f4971c4..000000000 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/Strategy.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.zalando.logbook.servlet; - -import org.apiguardian.api.API; -import org.zalando.logbook.Logbook; - -import javax.servlet.DispatcherType; -import javax.servlet.FilterChain; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -import static org.apiguardian.api.API.Status.MAINTAINED; -import static org.zalando.logbook.RequestFilters.replaceBody; - -@API(status = MAINTAINED) -public interface Strategy { - - Strategy NORMAL = new NormalStrategy(replaceBody(message -> "")); - Strategy SECURITY = new SecurityStrategy(replaceBody(message -> "")); - - void doFilter(Logbook logbook, HttpServletRequest httpRequest, HttpServletResponse httpResponse, - FilterChain chain) throws ServletException, IOException; - - default boolean isFirstRequest(final HttpServletRequest request) { - return request.getDispatcherType() != DispatcherType.ASYNC; - } - - default boolean isLastRequest(final HttpServletRequest request) { - return !request.isAsyncStarted(); - } - - -} - diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java deleted file mode 100644 index a65f014fa..000000000 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ErrorDispatchTest.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.zalando.logbook.servlet; - -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; -import org.springframework.test.web.servlet.MockMvc; -import org.springframework.test.web.servlet.setup.MockMvcBuilders; -import org.zalando.logbook.DefaultHttpLogFormatter; -import org.zalando.logbook.DefaultSink; -import org.zalando.logbook.HttpLogFormatter; -import org.zalando.logbook.HttpLogWriter; -import org.zalando.logbook.HttpMessage; -import org.zalando.logbook.HttpRequest; -import org.zalando.logbook.HttpResponse; -import org.zalando.logbook.Logbook; - -import javax.servlet.DispatcherType; -import java.io.IOException; - -import static java.util.Collections.emptyMap; -import static javax.servlet.RequestDispatcher.ERROR_EXCEPTION_TYPE; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; -import static org.hobsoft.hamcrest.compose.ComposeMatchers.hasFeature; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.reset; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; - -/** - * Verifies that {@link LogbookFilter} handles {@link DispatcherType#ERROR} correctly. - */ -final class ErrorDispatchTest { - - private final HttpLogFormatter formatter = spy(new ForwardingHttpLogFormatter(new DefaultHttpLogFormatter())); - private final HttpLogWriter writer = mock(HttpLogWriter.class); - - private final MockMvc mvc = MockMvcBuilders - .standaloneSetup(new ExampleController()) - .addFilter(new LogbookFilter(Logbook.builder() - .sink(new DefaultSink(formatter, writer)) - .build())) - .build(); - - @BeforeEach - void setUp() { - reset(formatter, writer); - - when(writer.isActive()).thenReturn(true); - } - - @Test - void shouldFormatErrorResponse() throws Exception { - mvc.perform(get("/api/not-found") - .content("Hello") - .requestAttr(ERROR_EXCEPTION_TYPE, "java.lang.Exception")); - - final HttpRequest request = interceptRequest(); - final HttpResponse response = interceptResponse(); - - assertThat(request, hasFeature(this::getBodyAsString, is(""))); - assertThat(response, hasFeature("status", HttpResponse::getStatus, is(404))); - assertThat(response, hasFeature("headers", HttpResponse::getHeaders, is(emptyMap()))); - } - - private String getBodyAsString(final HttpMessage message) { - try { - return message.getBodyAsString(); - } catch (final IOException e) { - throw new AssertionError(e); - } - } - - private HttpRequest interceptRequest() throws IOException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpRequest.class); - verify(formatter).format(any(), captor.capture()); - return captor.getValue(); - } - - private HttpResponse interceptResponse() throws IOException { - final ArgumentCaptor captor = ArgumentCaptor.forClass(HttpResponse.class); - verify(formatter).format(any(), captor.capture()); - return captor.getValue(); - } - -} diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java index 27bb60f51..53900041c 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/LogbookFilterTest.java @@ -3,7 +3,6 @@ import org.junit.jupiter.api.Test; import javax.servlet.FilterConfig; -import javax.servlet.ServletException; import static org.mockito.Mockito.mock; @@ -15,7 +14,12 @@ void shouldCreateLogbookFilter() { } @Test - void shouldCallInit() throws ServletException { + void shouldCreateSecureLogbookFilter() { + new SecureLogbookFilter(); + } + + @Test + void shouldCallInit() { new LogbookFilter().init(mock(FilterConfig.class)); } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java index ccbc1d185..b9359c78a 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java @@ -17,8 +17,6 @@ import org.zalando.logbook.Logbook; import org.zalando.logbook.Precorrelation; -import javax.servlet.Filter; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.not; @@ -47,14 +45,12 @@ final class MultiFilterSecurityTest { .sink(new DefaultSink(formatter, writer)) .build(); - private final Filter firstFilter = spy(new SpyableFilter(new LogbookFilter(logbook, Strategy.SECURITY))); - private final Filter lastFilter = spy(new SpyableFilter(new LogbookFilter(logbook))); private final ExampleController controller = spy(new ExampleController()); private final MockMvc mvc = MockMvcBuilders.standaloneSetup(controller) - .addFilter(firstFilter) + .addFilter(spy(new SpyableFilter(new SecureLogbookFilter(logbook)))) .addFilter(securityFilter) - .addFilter(lastFilter) + .addFilter(spy(new SpyableFilter(new LogbookFilter(logbook)))) .build(); @BeforeEach diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java index 7754d4f07..76f5b88d5 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java @@ -1,30 +1,43 @@ package org.zalando.logbook.servlet; import javax.annotation.Nullable; +import javax.servlet.Filter; import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -class SecurityFilter implements HttpFilter { +class SecurityFilter implements Filter { @Nullable private Integer status; + void setStatus(final Integer status) { + this.status = status; + } + @Override - public void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, - final FilterChain chain) - throws ServletException, IOException { + public void init(final FilterConfig filterConfig) { + // nothing to do + } + + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException { if (status == null) { - chain.doFilter(httpRequest, httpResponse); + chain.doFilter(request, response); } else { - httpResponse.setStatus(status); + ((HttpServletResponse) response).setStatus(status); } } - public void setStatus(final Integer status) { - this.status = status; + @Override + public void destroy() { + // nothing to do } + } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SpyableFilter.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SpyableFilter.java index ecac2acd0..896d62246 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SpyableFilter.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SpyableFilter.java @@ -1,25 +1,37 @@ package org.zalando.logbook.servlet; +import javax.servlet.Filter; import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; import java.io.IOException; // non final so we can wrap a spy around it -class SpyableFilter implements HttpFilter { +class SpyableFilter implements Filter { - private final HttpFilter filter; + private final Filter filter; - SpyableFilter(final HttpFilter filter) { + SpyableFilter(final Filter filter) { this.filter = filter; } @Override - public void doFilter(final HttpServletRequest request, final HttpServletResponse response, - final FilterChain chain) throws ServletException, IOException { + public void init(final FilterConfig filterConfig) throws ServletException { + filter.init(filterConfig); + } + + @Override + public void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws IOException, ServletException { filter.doFilter(request, response, chain); } + @Override + public void destroy() { + filter.destroy(); + } + } diff --git a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java index df621acd0..4d7dc07ff 100644 --- a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java +++ b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java @@ -42,14 +42,13 @@ import org.zalando.logbook.httpclient.LogbookHttpRequestInterceptor; import org.zalando.logbook.httpclient.LogbookHttpResponseInterceptor; import org.zalando.logbook.servlet.LogbookFilter; -import org.zalando.logbook.servlet.Strategy; +import org.zalando.logbook.servlet.SecureLogbookFilter; import javax.servlet.Filter; import java.util.List; import java.util.function.Predicate; import static javax.servlet.DispatcherType.ASYNC; -import static javax.servlet.DispatcherType.ERROR; import static javax.servlet.DispatcherType.REQUEST; import static org.apiguardian.api.API.Status.INTERNAL; import static org.apiguardian.api.API.Status.STABLE; @@ -254,17 +253,17 @@ public LogbookHttpResponseInterceptor logbookHttpResponseInterceptor() { @ConditionalOnWebApplication static class ServletFilterConfiguration { - private static final String FILTER_NAME = "authorizedLogbookFilter"; + private static final String FILTER_NAME = "secureLogbookFilter"; @Bean - @ConditionalOnProperty(name = "logbook.filter.enabled", havingValue = "true", matchIfMissing = true) + @ConditionalOnProperty(name = "logbook.secure-filter.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean(name = FILTER_NAME) - public FilterRegistrationBean authorizedLogbookFilter(final Logbook logbook) { - final Filter filter = new LogbookFilter(logbook); + public FilterRegistrationBean secureLogbookFilter(final Logbook logbook) { + final Filter filter = new SecureLogbookFilter(logbook); @SuppressWarnings("unchecked") // as of Spring Boot 2.x final FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setName(FILTER_NAME); - registration.setDispatcherTypes(REQUEST, ASYNC, ERROR); + registration.setDispatcherTypes(REQUEST, ASYNC); registration.setOrder(Ordered.LOWEST_PRECEDENCE); return registration; } @@ -280,22 +279,21 @@ public FilterRegistrationBean authorizedLogbookFilter(final Logbook logbook) { }) static class SecurityServletFilterConfiguration { - private static final String FILTER_NAME = "unauthorizedLogbookFilter"; + private static final String FILTER_NAME = "logbookFilter"; @Bean @ConditionalOnProperty(name = "logbook.filter.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean(name = FILTER_NAME) - public FilterRegistrationBean unauthorizedLogbookFilter(final Logbook logbook) { - final Filter filter = new LogbookFilter(logbook, Strategy.SECURITY); + public FilterRegistrationBean logbookFilter(final Logbook logbook) { + final Filter filter = new LogbookFilter(logbook); @SuppressWarnings("unchecked") // as of Spring Boot 2.x final FilterRegistrationBean registration = new FilterRegistrationBean(filter); registration.setName(FILTER_NAME); - registration.setDispatcherTypes(REQUEST, ASYNC, ERROR); + registration.setDispatcherTypes(REQUEST, ASYNC); registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); return registration; } } - } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FilterTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FilterTest.java index 8234542bc..f9a4f99fc 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FilterTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FilterTest.java @@ -13,12 +13,12 @@ class FilterTest { @Autowired - @Qualifier("authorizedLogbookFilter") - private FilterRegistrationBean authorizedLogbookFilter; + @Qualifier("logbookFilter") + private FilterRegistrationBean logbookFilter; @Test void shouldInitializeFilter() { - assertThat(authorizedLogbookFilter, is(notNullValue())); + assertThat(logbookFilter, is(notNullValue())); } } diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java index 4cba22569..461b55fea 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleCurlTest.java @@ -1,6 +1,5 @@ package org.zalando.logbook.spring; -import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java index 3110a89be..558b9ea9f 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleDefaultTest.java @@ -1,6 +1,5 @@ package org.zalando.logbook.spring; -import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java index b7a78cf97..ced0cdeba 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/FormatStyleHttpTest.java @@ -1,6 +1,5 @@ package org.zalando.logbook.spring; -import org.hamcrest.Matcher; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; diff --git a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/SecurityFilterTest.java b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/SecurityFilterTest.java index 7fdec90ed..37e9028af 100644 --- a/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/SecurityFilterTest.java +++ b/logbook-spring-boot-starter/src/test/java/org/zalando/logbook/spring/SecurityFilterTest.java @@ -13,17 +13,17 @@ class SecurityFilterTest { @Autowired - @Qualifier("unauthorizedLogbookFilter") - private FilterRegistrationBean unauthorizedLogbookFilter; + @Qualifier("secureLogbookFilter") + private FilterRegistrationBean secureLogbookFilter; @Autowired - @Qualifier("authorizedLogbookFilter") - private FilterRegistrationBean authorizedLogbookFilter; + @Qualifier("logbookFilter") + private FilterRegistrationBean logbookFilter; @Test void shouldInitializeFilters() { - assertThat(unauthorizedLogbookFilter, is(notNullValue())); - assertThat(authorizedLogbookFilter, is(notNullValue())); + assertThat(secureLogbookFilter, is(notNullValue())); + assertThat(logbookFilter, is(notNullValue())); } } From 77a0c9754cf07cd37a0a89774b3aea04e19b3677 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 09:42:03 +0100 Subject: [PATCH 07/13] Updated README and implemented missing pieces in auto configuraiton --- README.md | 40 +++++++++-- .../zalando/logbook/CurlHttpLogFormatter.java | 2 +- .../zalando/logbook/JsonHttpLogFormatter.java | 4 +- .../logbook/BodyOnlyIfErrorStrategy.java | 10 --- .../logbook/ErrorResponseOnlyStrategy.java | 10 --- .../zalando/logbook/ResponseOnlyStrategy.java | 18 ----- .../SomeRequestsWithoutBodyStrategy.java | 15 ----- ...Strategy.java => WithoutBodyStrategy.java} | 10 +-- .../spring/LogbookAutoConfiguration.java | 66 ++++++++++++------- 9 files changed, 85 insertions(+), 90 deletions(-) delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java delete mode 100644 logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java rename logbook-core/src/test/java/org/zalando/logbook/{RequestOnlyStrategy.java => WithoutBodyStrategy.java} (53%) diff --git a/README.md b/README.md index 6205cf286..24f4fd97c 100644 --- a/README.md +++ b/README.md @@ -76,10 +76,6 @@ Alternatively, you can import our *bill of materials*... org.zalando logbook-core - - org.zalando - logbook-servlet - org.zalando logbook-httpclient @@ -96,6 +92,10 @@ Alternatively, you can import our *bill of materials*... org.zalando logbook-okhttp2 + + org.zalando + logbook-servlet + org.zalando logbook-spring-boot-starter @@ -133,6 +133,28 @@ Logbook logbook = Logbook.builder() .build(); ``` +### Strategy + +Logbook used to have a very rigid strategy how to do request/response logging: + +- Requests/responses are logged separately +- Requests/responses are logged soon as possible +- Requests/responses are logged as a pair or not logged at all + (i.e. no partial logging of traffic) + +Some of those restrictions could be mitigated with custom [`HttpLogWriter`](#writing) +implementations, but they were never ideal. + +Starting with version 2.0 Logbook now comes with a [Strategy pattern](https://en.wikipedia.org/wiki/Strategy_pattern) +at its core. Make sure you read the documentation of the [`Strategy`](logbook-api/src/main/java/org/zalando/logbook/Strategy.java) +interface to understand the implications. + +Some example implementations can be found here: + +- [`BodyOnlyIfErrorStrategy`](logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java) +- [`ErrorResponseOnlyStrategy`](logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java) +- [`WithoutBodyStrategy`](logbook-core/src/test/java/org/zalando/logbook/WithoutBodyStrategy.java) + ### Phases Logbook works in several different phases: @@ -376,6 +398,14 @@ Logbook logbook = Logbook.builder() ``` +#### Sink + +The combination of `HttpLogFormatter` and `HttpLogWriter` suits most use cases well, but it has limitations. +Implementing the `Sink` interface directly allows for more sophisticated use cases, e.g. writing requests/responses +to a structured persistent storage like a database. + +Multiple sinks can be combined into one using the `CompositeSink`. + ### Servlet You’ll have to register the `LogbookFilter` as a `Filter` in your filter chain — either in your `web.xml` file (please note that the xml approach will use all the defaults and is not configurable): @@ -529,6 +559,8 @@ Logbook comes with a convenient auto configuration for Spring Boot users. It set | `BodyFilter` | | `BodyFilters.defaultValue()` | | `RequestFilter` | | `RequestFilter.none()` | | `ResponseFilter` | | `ResponseFilter.none()` | +| `Strategy` | | `DefaultStrategy` | +| `Sink` | | `DefaultSink` | | `HttpLogFormatter` | | `JsonHttpLogFormatter` | | `HttpLogWriter` | | `DefaultHttpLogWriter` | diff --git a/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java b/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java index 86d8b1729..d9797683e 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/CurlHttpLogFormatter.java @@ -52,7 +52,7 @@ public String format(final Precorrelation precorrelation, final HttpRequest requ command.add(quote(body)); } - return command.stream().collect(joining(" ")); + return String.join(" ", command); } private static String quote(final String s) { diff --git a/logbook-core/src/main/java/org/zalando/logbook/JsonHttpLogFormatter.java b/logbook-core/src/main/java/org/zalando/logbook/JsonHttpLogFormatter.java index 889d38623..ce44ddbcc 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/JsonHttpLogFormatter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/JsonHttpLogFormatter.java @@ -30,13 +30,13 @@ * * public String format(final Precorrelation precorrelation) throws IOException { * Map request = delegate.prepare(precorrelation); - * // TODO modify request here + * // modify request here * return delegate.format(request); * } * * public String format(final Correlation correlation) throws IOException { * Map response = delegate.prepare(correlation); - * // TODO modify response here + * // modify response here * return delegate.format(response); * } * diff --git a/logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java index aa1e2dba8..7a9e38e82 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/BodyOnlyIfErrorStrategy.java @@ -7,22 +7,12 @@ */ final class BodyOnlyIfErrorStrategy implements Strategy { - @Override - public HttpRequest process(final HttpRequest request) throws IOException { - return request.withBody(); - } - @Override public void write(final Precorrelation precorrelation, final HttpRequest request, final Sink sink) { // do nothing } - @Override - public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { - return response.withBody(); - } - @Override public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, final Sink sink) throws IOException { diff --git a/logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java index 942478fa8..b808b9b1f 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/ErrorResponseOnlyStrategy.java @@ -7,22 +7,12 @@ */ final class ErrorResponseOnlyStrategy implements Strategy { - @Override - public HttpRequest process(final HttpRequest request) throws IOException { - return request.withBody(); - } - @Override public void write(final Precorrelation precorrelation, final HttpRequest request, final Sink sink) { // do nothing } - @Override - public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { - return response.withBody(); - } - @Override public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, final Sink sink) throws IOException { diff --git a/logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java deleted file mode 100644 index 76a2956b6..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/ResponseOnlyStrategy.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.zalando.logbook; - -/** - * Proof of concept - */ -final class ResponseOnlyStrategy implements Strategy { - - @Override - public HttpRequest process(final HttpRequest request) { - return request.withoutBody(); - } - - @Override - public void write(final Precorrelation precorrelation, final HttpRequest request, final Sink sink) { - // do nothing - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java deleted file mode 100644 index a1418ad2c..000000000 --- a/logbook-core/src/test/java/org/zalando/logbook/SomeRequestsWithoutBodyStrategy.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.zalando.logbook; - -import java.io.IOException; - -/** - * Proof of concept - */ -final class SomeRequestsWithoutBodyStrategy implements Strategy { - - @Override - public HttpRequest process(final HttpRequest request) throws IOException { - return request.getRequestUri().contains("/attachments") ? request.withoutBody() : request.withBody(); - } - -} diff --git a/logbook-core/src/test/java/org/zalando/logbook/RequestOnlyStrategy.java b/logbook-core/src/test/java/org/zalando/logbook/WithoutBodyStrategy.java similarity index 53% rename from logbook-core/src/test/java/org/zalando/logbook/RequestOnlyStrategy.java rename to logbook-core/src/test/java/org/zalando/logbook/WithoutBodyStrategy.java index 8da913380..18bb4743d 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/RequestOnlyStrategy.java +++ b/logbook-core/src/test/java/org/zalando/logbook/WithoutBodyStrategy.java @@ -3,16 +3,16 @@ /** * Proof of concept */ -final class RequestOnlyStrategy implements Strategy { +final class WithoutBodyStrategy implements Strategy { @Override - public HttpResponse process(final HttpRequest request, final HttpResponse response) { - return response.withoutBody(); + public HttpRequest process(final HttpRequest request) { + return request.withoutBody(); } @Override - public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, final Sink sink) { - // do nothing + public HttpResponse process(final HttpRequest request, final HttpResponse response) { + return response.withoutBody(); } } diff --git a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java index 4d7dc07ff..215981839 100644 --- a/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java +++ b/logbook-spring-boot-starter/src/main/java/org/zalando/logbook/spring/LogbookAutoConfiguration.java @@ -27,6 +27,7 @@ import org.zalando.logbook.DefaultHttpLogFormatter; import org.zalando.logbook.DefaultHttpLogWriter; import org.zalando.logbook.DefaultSink; +import org.zalando.logbook.DefaultStrategy; import org.zalando.logbook.HeaderFilter; import org.zalando.logbook.HeaderFilters; import org.zalando.logbook.HttpLogFormatter; @@ -38,7 +39,9 @@ import org.zalando.logbook.QueryFilters; import org.zalando.logbook.RequestFilter; import org.zalando.logbook.ResponseFilter; +import org.zalando.logbook.Sink; import org.zalando.logbook.SplunkHttpLogFormatter; +import org.zalando.logbook.Strategy; import org.zalando.logbook.httpclient.LogbookHttpRequestInterceptor; import org.zalando.logbook.httpclient.LogbookHttpResponseInterceptor; import org.zalando.logbook.servlet.LogbookFilter; @@ -83,8 +86,8 @@ public Logbook logbook( final List bodyFilters, final List requestFilters, final List responseFilters, - @SuppressWarnings("SpringJavaAutowiringInspection") final HttpLogFormatter formatter, - final HttpLogWriter writer) { + final Strategy strategy, + final Sink sink) { return Logbook.builder() .condition(mergeWithExcludes(mergeWithIncludes(condition))) @@ -93,8 +96,8 @@ public Logbook logbook( .bodyFilters(bodyFilters) .requestFilters(requestFilters) .responseFilters(responseFilters) - // TODO strategy - .sink(new DefaultSink(formatter, writer)) + .strategy(strategy) + .sink(sink) .build(); } @@ -174,6 +177,22 @@ public ResponseFilter responseFilter() { return ResponseFilter.none(); } + @API(status = INTERNAL) + @Bean + @ConditionalOnMissingBean(Strategy.class) + public Strategy strategy() { + return new DefaultStrategy(); + } + + @API(status = INTERNAL) + @Bean + @ConditionalOnMissingBean(Sink.class) + public Sink sink( + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") final HttpLogFormatter formatter, + final HttpLogWriter writer) { + return new DefaultSink(formatter, writer); + } + @API(status = INTERNAL) @Bean @ConditionalOnMissingBean(HttpLogFormatter.class) @@ -203,7 +222,7 @@ public HttpLogFormatter splunkHttpLogFormatter() { @ConditionalOnBean(ObjectMapper.class) @ConditionalOnMissingBean(HttpLogFormatter.class) public HttpLogFormatter jsonFormatter( - @SuppressWarnings("SpringJavaAutowiringInspection") final ObjectMapper mapper) { + @SuppressWarnings("SpringJavaInjectionPointsAutowiringInspection") final ObjectMapper mapper) { return new JsonHttpLogFormatter(mapper); } @@ -253,19 +272,13 @@ public LogbookHttpResponseInterceptor logbookHttpResponseInterceptor() { @ConditionalOnWebApplication static class ServletFilterConfiguration { - private static final String FILTER_NAME = "secureLogbookFilter"; + private static final String FILTER_NAME = "logbookFilter"; @Bean - @ConditionalOnProperty(name = "logbook.secure-filter.enabled", havingValue = "true", matchIfMissing = true) + @ConditionalOnProperty(name = "logbook.filter.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean(name = FILTER_NAME) - public FilterRegistrationBean secureLogbookFilter(final Logbook logbook) { - final Filter filter = new SecureLogbookFilter(logbook); - @SuppressWarnings("unchecked") // as of Spring Boot 2.x - final FilterRegistrationBean registration = new FilterRegistrationBean(filter); - registration.setName(FILTER_NAME); - registration.setDispatcherTypes(REQUEST, ASYNC); - registration.setOrder(Ordered.LOWEST_PRECEDENCE); - return registration; + public FilterRegistrationBean logbookFilter(final Logbook logbook) { + return newFilter(new LogbookFilter(logbook), FILTER_NAME, Ordered.LOWEST_PRECEDENCE); } } @@ -279,21 +292,24 @@ public FilterRegistrationBean secureLogbookFilter(final Logbook logbook) { }) static class SecurityServletFilterConfiguration { - private static final String FILTER_NAME = "logbookFilter"; + private static final String FILTER_NAME = "secureLogbookFilter"; @Bean - @ConditionalOnProperty(name = "logbook.filter.enabled", havingValue = "true", matchIfMissing = true) + @ConditionalOnProperty(name = "logbook.secure-filter.enabled", havingValue = "true", matchIfMissing = true) @ConditionalOnMissingBean(name = FILTER_NAME) - public FilterRegistrationBean logbookFilter(final Logbook logbook) { - final Filter filter = new LogbookFilter(logbook); - @SuppressWarnings("unchecked") // as of Spring Boot 2.x - final FilterRegistrationBean registration = new FilterRegistrationBean(filter); - registration.setName(FILTER_NAME); - registration.setDispatcherTypes(REQUEST, ASYNC); - registration.setOrder(Ordered.HIGHEST_PRECEDENCE + 1); - return registration; + public FilterRegistrationBean secureLogbookFilter(final Logbook logbook) { + return newFilter(new SecureLogbookFilter(logbook), FILTER_NAME, Ordered.HIGHEST_PRECEDENCE + 1); } } + private static FilterRegistrationBean newFilter(final Filter filter, final String filterName, final int order) { + @SuppressWarnings("unchecked") // as of Spring Boot 2.x + final FilterRegistrationBean registration = new FilterRegistrationBean(filter); + registration.setName(filterName); + registration.setDispatcherTypes(REQUEST, ASYNC); + registration.setOrder(order); + return registration; + } + } From c621932d58cd4f53c0ea361a6740e9f145ef0112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 10:40:32 +0100 Subject: [PATCH 08/13] Simplified servlet filter implementation (again...) --- .../logbook/jaxrs/LogbookServerFilter.java | 1 + .../servlet/AbstractLogbookFilter.java | 92 ------------------- .../zalando/logbook/servlet/HttpFilter.java | 42 +++++++++ .../logbook/servlet/LogbookFilter.java | 30 +++++- .../logbook/servlet/RemoteRequest.java | 6 +- .../logbook/servlet/SecureLogbookFilter.java | 34 ++++++- .../logbook/servlet/SecurityFilter.java | 23 +---- 7 files changed, 107 insertions(+), 121 deletions(-) delete mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java create mode 100644 logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java diff --git a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java index 83ebacc4f..1a850ba24 100644 --- a/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java +++ b/logbook-jaxrs/src/main/java/org/zalando/logbook/jaxrs/LogbookServerFilter.java @@ -20,6 +20,7 @@ import static org.zalando.fauxpas.FauxPas.throwingConsumer; +// TODO SecureLogbookServerFilter which handles unauthorized requests? @Provider @ConstrainedTo(RuntimeType.SERVER) public final class LogbookServerFilter implements ContainerRequestFilter, ContainerResponseFilter, WriterInterceptor { diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java deleted file mode 100644 index d7cdbfdca..000000000 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/AbstractLogbookFilter.java +++ /dev/null @@ -1,92 +0,0 @@ -package org.zalando.logbook.servlet; - -import org.zalando.logbook.HttpRequest; -import org.zalando.logbook.Logbook; -import org.zalando.logbook.Logbook.ResponseProcessingStage; -import org.zalando.logbook.RequestFilter; - -import javax.servlet.DispatcherType; -import javax.servlet.Filter; -import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; -import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; - -import static org.zalando.logbook.RequestFilters.replaceBody; - -abstract class AbstractLogbookFilter implements Filter { - - private static final String STAGE = ResponseProcessingStage.class.getName(); - - private final RequestFilter filter = replaceBody(message -> ""); - private final Logbook logbook; - - protected AbstractLogbookFilter(final Logbook logbook) { - this.logbook = logbook; - } - - @Override - public final void init(final FilterConfig filterConfig) { - // no initialization needed by default - } - - @Override - public final void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws ServletException, IOException { - - if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { - throw new IllegalArgumentException(getClass().getSimpleName() + " only supports HTTP"); - } - - final HttpServletRequest httpRequest = (HttpServletRequest) request; - final HttpServletResponse httpResponse = (HttpServletResponse) response; - - doFilter(httpRequest, httpResponse, chain); - } - - protected abstract void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, - final FilterChain chain) throws ServletException, IOException; - - protected final ResponseProcessingStage logRequest(final HttpServletRequest httpRequest, - final HttpRequest request) throws IOException { - - if (isFirstRequest(httpRequest)) { - final ResponseProcessingStage stage = logbook.process(request).write(); - httpRequest.setAttribute(STAGE, stage); - return stage; - } else { - return (ResponseProcessingStage) httpRequest.getAttribute(STAGE); - } - } - - protected final void logResponse(final RemoteRequest request, final LocalResponse response, - final Logbook.ResponseWritingStage stage) throws IOException { - - if (isLastRequest(request)) { - response.getWriter().flush(); - stage.write(); - } - } - - protected final boolean isFirstRequest(final HttpServletRequest request) { - return request.getDispatcherType() != DispatcherType.ASYNC; - } - - protected final boolean isLastRequest(final HttpServletRequest request) { - return !request.isAsyncStarted(); - } - - protected final HttpRequest skipBody(final RemoteRequest request) { - return filter.filter(request); - } - - @Override - public final void destroy() { - // no deconstruction needed by default - } - -} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java new file mode 100644 index 000000000..37359b644 --- /dev/null +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/HttpFilter.java @@ -0,0 +1,42 @@ +package org.zalando.logbook.servlet; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; + +interface HttpFilter extends Filter { + + @Override + default void init(final FilterConfig filterConfig) { + // no initialization needed by default + } + + @Override + default void doFilter(final ServletRequest request, final ServletResponse response, + final FilterChain chain) throws ServletException, IOException { + + if (!(request instanceof HttpServletRequest) || !(response instanceof HttpServletResponse)) { + throw new IllegalArgumentException(getClass().getSimpleName() + " only supports HTTP"); + } + + final HttpServletRequest httpRequest = (HttpServletRequest) request; + final HttpServletResponse httpResponse = (HttpServletResponse) response; + + doFilter(httpRequest, httpResponse, chain); + } + + void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, + final FilterChain chain) throws ServletException, IOException; + + @Override + default void destroy() { + // no deconstruction needed by default + } + +} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java index 01bf116a0..a400124e5 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java @@ -1,9 +1,12 @@ package org.zalando.logbook.servlet; import org.apiguardian.api.API; +import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.ResponseProcessingStage; import org.zalando.logbook.Logbook.ResponseWritingStage; +import javax.servlet.DispatcherType; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -13,14 +16,18 @@ import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public final class LogbookFilter extends AbstractLogbookFilter { +public final class LogbookFilter implements HttpFilter { + + private static final String STAGE = ResponseProcessingStage.class.getName(); + + private final Logbook logbook; public LogbookFilter() { this(Logbook.create()); } public LogbookFilter(final Logbook logbook) { - super(logbook); + this.logbook = logbook; } @Override @@ -34,7 +41,24 @@ public void doFilter(final HttpServletRequest httpRequest, final HttpServletResp chain.doFilter(request, response); - logResponse(request, response, stage); + if (request.isAsyncStarted()) { + return; + } + + response.getWriter().flush(); + stage.write(); + } + + private ResponseProcessingStage logRequest(final HttpServletRequest httpRequest, + final HttpRequest request) throws IOException { + + if (httpRequest.getDispatcherType() == DispatcherType.ASYNC) { + return (ResponseProcessingStage) httpRequest.getAttribute(STAGE); + } else { + final ResponseProcessingStage stage = logbook.process(request).write(); + httpRequest.setAttribute(STAGE, stage); + return stage; + } } } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java index 937f24c4c..ffe99e8cc 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java @@ -148,10 +148,8 @@ static String encode(final String s, final String charset) { } @Override - public ServletInputStream getInputStream() throws IOException { - return body == null ? - super.getInputStream() : - new ServletInputStreamAdapter(new ByteArrayInputStream(body)); + public ServletInputStream getInputStream() { + return new ServletInputStreamAdapter(new ByteArrayInputStream(body)); } @Override diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java index 35bb0333f..6a50871da 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java @@ -2,8 +2,10 @@ import org.apiguardian.api.API; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.RequestWritingStage; import org.zalando.logbook.Logbook.ResponseWritingStage; +import javax.servlet.DispatcherType; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -13,14 +15,18 @@ import static org.apiguardian.api.API.Status.STABLE; @API(status = STABLE) -public final class SecureLogbookFilter extends AbstractLogbookFilter { +public final class SecureLogbookFilter implements HttpFilter { + + private static final String STAGE = RequestWritingStage.class.getName(); + + private final Logbook logbook; public SecureLogbookFilter() { this(Logbook.create()); } public SecureLogbookFilter(final Logbook logbook) { - super(logbook); + this.logbook = logbook; } @Override @@ -30,11 +36,31 @@ public void doFilter(final HttpServletRequest httpRequest, final HttpServletResp final RemoteRequest request = new RemoteRequest(httpRequest); final LocalResponse response = new LocalResponse(httpResponse, request.getProtocolVersion()); + final RequestWritingStage writeRequest = processRequest(httpRequest, request); + + // effectively overriding the decision from the underlying strategy + request.withoutBody(); + chain.doFilter(request, response); + if (request.isAsyncStarted()) { + return; + } + if (isUnauthorizedOrForbidden(response)) { - final ResponseWritingStage stage = logRequest(request, skipBody(request)).process(response); - logResponse(request, response, stage); + final ResponseWritingStage process = writeRequest.write().process(response); + response.getWriter().flush(); + process.write(); + } + } + + private RequestWritingStage processRequest(final HttpServletRequest httpRequest, final RemoteRequest request) throws IOException { + if (httpRequest.getDispatcherType() == DispatcherType.ASYNC) { + return (RequestWritingStage) httpRequest.getAttribute(STAGE); + } else { + final RequestWritingStage stage = logbook.process(request); + httpRequest.setAttribute(STAGE, stage); + return stage; } } diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java index 76f5b88d5..47cc2884f 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/SecurityFilter.java @@ -1,16 +1,13 @@ package org.zalando.logbook.servlet; import javax.annotation.Nullable; -import javax.servlet.Filter; import javax.servlet.FilterChain; -import javax.servlet.FilterConfig; import javax.servlet.ServletException; -import javax.servlet.ServletRequest; -import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -class SecurityFilter implements Filter { +class SecurityFilter implements HttpFilter { @Nullable private Integer status; @@ -20,24 +17,14 @@ void setStatus(final Integer status) { } @Override - public void init(final FilterConfig filterConfig) { - // nothing to do - } - - @Override - public void doFilter(final ServletRequest request, final ServletResponse response, - final FilterChain chain) throws IOException, ServletException { + public void doFilter(final HttpServletRequest request, final HttpServletResponse response, + final FilterChain chain) throws ServletException, IOException { if (status == null) { chain.doFilter(request, response); } else { - ((HttpServletResponse) response).setStatus(status); + response.setStatus(status); } } - @Override - public void destroy() { - // nothing to do - } - } From 4619d350e5b1ee09696a38e7718a898e0ec34046 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 13:19:02 +0100 Subject: [PATCH 09/13] Fixed CompositeSink --- .../org/zalando/logbook/CompositeSink.java | 14 +++--- .../zalando/logbook/CompositeSinkTest.java | 45 ++++++++++++++++++- 2 files changed, 53 insertions(+), 6 deletions(-) diff --git a/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java b/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java index 4c0742c27..9325036fd 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java +++ b/logbook-core/src/main/java/org/zalando/logbook/CompositeSink.java @@ -1,11 +1,11 @@ package org.zalando.logbook; import lombok.AllArgsConstructor; +import org.zalando.fauxpas.ThrowingConsumer; +import java.io.IOException; import java.util.Collection; -import static org.zalando.fauxpas.FauxPas.throwingConsumer; - @AllArgsConstructor public final class CompositeSink implements Sink { @@ -18,17 +18,21 @@ public boolean isActive() { @Override public void write(final Precorrelation precorrelation, final HttpRequest request) { - sinks.forEach(throwingConsumer(sink -> sink.write(precorrelation, request))); + each(sink -> sink.write(precorrelation, request)); } @Override public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response) { - sinks.forEach(throwingConsumer(sink -> sink.write(correlation, request, response))); + each(sink -> sink.write(correlation, request, response)); } @Override public void writeBoth(final Correlation correlation, final HttpRequest request, final HttpResponse response) { - sinks.forEach(throwingConsumer(sink -> sink.writeBoth(correlation, request, response))); + each(sink -> sink.writeBoth(correlation, request, response)); + } + + private void each(final ThrowingConsumer consumer) { + sinks.stream().filter(Sink::isActive).forEach(consumer); } } diff --git a/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java b/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java index 96e3a7392..6f424d0d1 100644 --- a/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java +++ b/logbook-core/src/test/java/org/zalando/logbook/CompositeSinkTest.java @@ -8,6 +8,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -48,6 +49,9 @@ void isInactiveIfNone() { @Test void writeRequestToAll() throws IOException { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(true); + unit.write(precorrelation, request); verify(first).write(precorrelation, request); @@ -56,6 +60,9 @@ void writeRequestToAll() throws IOException { @Test void writeResponseToAll() throws IOException { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(true); + unit.write(correlation, request, response); verify(first).write(correlation, request, response); @@ -63,11 +70,47 @@ void writeResponseToAll() throws IOException { } @Test - void writeBoth() throws IOException { + void writeBothToAll() throws IOException { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(true); + unit.writeBoth(correlation, request, response); verify(first).writeBoth(correlation, request, response); verify(second).writeBoth(correlation, request, response); } + @Test + void writeRequestToActive() throws IOException { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(false); + + unit.write(precorrelation, request); + + verify(first).write(precorrelation, request); + verify(second, never()).write(precorrelation, request); + } + + @Test + void writeResponseToActive() throws IOException { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(false); + + unit.write(correlation, request, response); + + verify(first).write(correlation, request, response); + verify(second, never()).write(correlation, request, response); + } + + @Test + void writeBothToActive() throws IOException { + when(first.isActive()).thenReturn(true); + when(second.isActive()).thenReturn(false); + + unit.writeBoth(correlation, request, response); + + verify(first).writeBoth(correlation, request, response); + verify(second, never()).writeBoth(correlation, request, response); + } + } From 3b640015885a391af4290bb3f11f2af694567c49 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 14:04:00 +0100 Subject: [PATCH 10/13] Added docs to Strategy interface --- .../java/org/zalando/logbook/Strategy.java | 92 +++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/logbook-api/src/main/java/org/zalando/logbook/Strategy.java b/logbook-api/src/main/java/org/zalando/logbook/Strategy.java index 163251426..7989a9b73 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Strategy.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Strategy.java @@ -6,22 +6,114 @@ import static org.apiguardian.api.API.Status.STABLE; +/** + * A strategy is glue between {@link Logbook} integrations and the {@link Sink}. The lifecycle of a request-response + * pair will invoke methods in the following order: + * + *

    + *
  1. {@link Strategy#process(HttpRequest)}
  2. + *
  3. {@link Strategy#write(Precorrelation, HttpRequest, Sink)}
  4. + *
  5. {@link Strategy#process(HttpRequest, HttpResponse)}
  6. + *
  7. {@link Strategy#write(Correlation, HttpRequest, HttpResponse, Sink)}
  8. + *
+ * + * At each of those points in time different options are available, e.g. to defer logging, apply conditions or even + * modify something. + * + * Strategy pattern + */ @API(status = STABLE) public interface Strategy { + /** + * This method is being called right before the request body is being buffered. The primary goal of this method is + * to decide whether the body should be recorded or not. + * + * Defaults to {@link HttpRequest#withBody()}. + * + * @see HttpRequest#withBody() + * @see HttpRequest#withoutBody() + * @param request the current request + * @return the given request + * @throws IOException see {@link HttpRequest#withBody()} + */ default HttpRequest process(final HttpRequest request) throws IOException { return request.withBody(); } + /** + * This method is being called right after the response body was buffered. The primary goal of this method is to + * decide whether and if then how the request is being logged. + * + * Options include but are not limited to: + * + *
    + *
  • Log request immediately.
  • + *
  • Defer logging to a later point in time (e.g. when the response becomes available).
  • + *
  • Log request conditionally.
  • + *
  • Log request without body. (Performance penalty for buffering still applies!)
  • + *
+ * + * + * Defaults to delegating to {@link Sink#write(Precorrelation, HttpRequest)}. + * + * @see Sink#write(Precorrelation, HttpRequest) + * @param precorrelation a preliminary {@link Correlation correlation} which provides an id to correlate request + * and response later + * @param request the current request + * @param sink the sink to write to, if needed + * @throws IOException see {@link Sink#write(Precorrelation, HttpRequest)} + */ default void write(final Precorrelation precorrelation, final HttpRequest request, final Sink sink) throws IOException { sink.write(precorrelation, request); } + /** + * This method is being called right before the response body is being buffered. The primary goal of this method is + * to decide whether the body should be recorded or not. Beware that the response may or may not + * be a reliable source of information since it was fully processed yet. Any decision whether to buffer the body + * or not should be made exclusively based on the provided {@link HttpRequest request}. + * + * Defaults to {@link HttpResponse#withBody()}. + * + * @see HttpResponse#withBody() + * @see HttpResponse#withoutBody() + * @param request the current request + * @param response the current response + * @return the given response + * @throws IOException see {@link HttpResponse#withBody()} + */ default HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { return response.withBody(); } + /** + * This method is being called right after the response body was buffered. The primary goal of this method is to + * decide whether and if then how the response (and optionally also the request) is being logged. + * + * Options include but are not limited to: + * + *
    + *
  • Log response immediately.
  • + *
  • Log response conditionally.
  • + *
  • Log response without body. (Performance penalty for buffering still applies!)
  • + *
  • Log request now, instead of earlier.
  • + *
  • Log request conditionally based on the response.
  • + *
  • Log request without body. (Performance penalty for buffering still applies!)
  • + *
+ * + * Defaults to delegating to {@link Sink#write(Correlation, HttpRequest, HttpResponse)}. + * + * @see Sink#write(Correlation, HttpRequest, HttpResponse) + * @see Sink#writeBoth(Correlation, HttpRequest, HttpResponse) + * @param correlation a correlation which provides and id (as well as a duration) to correlate request and response + * later + * @param request the current request + * @param response the current response + * @param sink the sink to write to, if needed + * @throws IOException see {@link Sink#write(Correlation, HttpRequest, HttpResponse)} + */ default void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, final Sink sink) throws IOException { sink.write(correlation, request, response); From 470d5d58cf4952e0f68cf5a53aaca5dcf0463609 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 20:42:26 +0100 Subject: [PATCH 11/13] Introduced SecurityStrategy --- .../java/org/zalando/logbook/Logbook.java | 1 + .../java/org/zalando/logbook/Mockbook.java | 6 ++ .../org/zalando/logbook/DefaultLogbook.java | 9 ++- .../org/zalando/logbook/SecurityStrategy.java | 33 +++++++++++ .../main/java/org/zalando/logbook/Stages.java | 19 ++++++ .../zalando/logbook/SecurityStrategyTest.java | 58 +++++++++++++++++++ .../logbook/servlet/LogbookFilter.java | 17 +++++- .../logbook/servlet/RemoteRequest.java | 1 + .../logbook/servlet/SecureLogbookFilter.java | 47 ++------------- 9 files changed, 145 insertions(+), 46 deletions(-) create mode 100644 logbook-core/src/main/java/org/zalando/logbook/SecurityStrategy.java create mode 100644 logbook-core/src/main/java/org/zalando/logbook/Stages.java create mode 100644 logbook-core/src/test/java/org/zalando/logbook/SecurityStrategyTest.java diff --git a/logbook-api/src/main/java/org/zalando/logbook/Logbook.java b/logbook-api/src/main/java/org/zalando/logbook/Logbook.java index ab86e04d2..46227d011 100644 --- a/logbook-api/src/main/java/org/zalando/logbook/Logbook.java +++ b/logbook-api/src/main/java/org/zalando/logbook/Logbook.java @@ -10,6 +10,7 @@ public interface Logbook { RequestWritingStage process(HttpRequest request) throws IOException; + RequestWritingStage process(HttpRequest request, Strategy strategy) throws IOException; interface RequestWritingStage { ResponseProcessingStage write() throws IOException; diff --git a/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java b/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java index 5c413bc33..4c96e3d62 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java +++ b/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java @@ -3,6 +3,7 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import java.io.IOException; import java.util.function.Predicate; @AllArgsConstructor @@ -23,4 +24,9 @@ public RequestWritingStage process(final HttpRequest request) { throw new UnsupportedOperationException(); } + @Override + public RequestWritingStage process(final HttpRequest request, final Strategy strategy) { + throw new UnsupportedOperationException(); + } + } diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java index 5f5f26dd2..61ea14402 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultLogbook.java @@ -21,6 +21,11 @@ final class DefaultLogbook implements Logbook { @Override public RequestWritingStage process(final HttpRequest originalRequest) throws IOException { + return process(originalRequest, strategy); + } + + @Override + public RequestWritingStage process(final HttpRequest originalRequest, final Strategy strategy) throws IOException { if (sink.isActive() && predicate.test(originalRequest)) { final Precorrelation precorrelation = new SimplePrecorrelation(clock); final HttpRequest processedRequest = strategy.process(originalRequest); @@ -37,9 +42,7 @@ public RequestWritingStage process(final HttpRequest originalRequest) throws IOE }; }; } else { - return () -> response -> () -> { - // nothing to do - }; + return Stages.noop(); } } diff --git a/logbook-core/src/main/java/org/zalando/logbook/SecurityStrategy.java b/logbook-core/src/main/java/org/zalando/logbook/SecurityStrategy.java new file mode 100644 index 000000000..af703fbd1 --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/SecurityStrategy.java @@ -0,0 +1,33 @@ +package org.zalando.logbook; + +import java.io.IOException; + +/** + * A {@link SecurityStrategy} is a {@link Strategy strategy} which is meant to be used in server-side environments to + * give the best possible compromise between security and observability. + * + * This strategy discards requests bodies. + */ +public final class SecurityStrategy implements Strategy { + + @Override + public HttpRequest process(final HttpRequest request) { + return request.withoutBody(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request, final Sink sink) { + // defer decision until response is available + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, final HttpResponse response, + final Sink sink) throws IOException { + + final int status = response.getStatus(); + if (status == 401 || status == 403) { + sink.writeBoth(correlation, request, response); + } + } + +} diff --git a/logbook-core/src/main/java/org/zalando/logbook/Stages.java b/logbook-core/src/main/java/org/zalando/logbook/Stages.java new file mode 100644 index 000000000..4136eb6d5 --- /dev/null +++ b/logbook-core/src/main/java/org/zalando/logbook/Stages.java @@ -0,0 +1,19 @@ +package org.zalando.logbook; + +import org.zalando.logbook.Logbook.RequestWritingStage; + +final class Stages { + + private static final Logbook.ResponseWritingStage WRITE_RESPONSE = () -> {}; + private static final Logbook.ResponseProcessingStage PROCESS_RESPONE = response -> WRITE_RESPONSE; + private static final RequestWritingStage WRITE_REQUEST = () -> PROCESS_RESPONE; + + private Stages() { + + } + + static RequestWritingStage noop() { + return WRITE_REQUEST; + } + +} diff --git a/logbook-core/src/test/java/org/zalando/logbook/SecurityStrategyTest.java b/logbook-core/src/test/java/org/zalando/logbook/SecurityStrategyTest.java new file mode 100644 index 000000000..346b3e5f0 --- /dev/null +++ b/logbook-core/src/test/java/org/zalando/logbook/SecurityStrategyTest.java @@ -0,0 +1,58 @@ +package org.zalando.logbook; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.io.IOException; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +class SecurityStrategyTest { + + private final Sink sink = mock(Sink.class); + + private final Logbook unit = Logbook.builder() + .strategy(new SecurityStrategy()) + .sink(sink) + .build(); + + private final MockHttpRequest request = MockHttpRequest.create(); + private final MockHttpResponse response = MockHttpResponse.create(); + + @BeforeEach + void defaultBehaviour() { + when(sink.isActive()).thenReturn(true); + } + + @Test + void shouldDeferWritingOfRequest() throws IOException { + unit.process(request).write(); + + verify(sink, never()).write(any(), any()); + } + + @ParameterizedTest + @ValueSource(ints = {200, 201, 301, 400, 404, 500}) + void shouldNotWriteAnythingWhenSuccessfullyAuthenticatedAndAuthorized(int status) throws IOException { + unit.process(request).write().process(response.withStatus(status)).write(); + + verify(sink, never()).write(any(), any()); + verify(sink, never()).write(any(), any(), any()); + verify(sink, never()).writeBoth(any(), any(), any()); + } + + @ParameterizedTest + @ValueSource(ints = {401, 403}) + void shouldLogBothWhenForbidden(final int status) throws IOException { + unit.process(request).write().process(response.withStatus(status)).write(); + + verify(sink).writeBoth(any(), any(), any()); + } + +} diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java index a400124e5..d63ff69ab 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LogbookFilter.java @@ -3,9 +3,12 @@ import org.apiguardian.api.API; import org.zalando.logbook.HttpRequest; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Logbook.RequestWritingStage; import org.zalando.logbook.Logbook.ResponseProcessingStage; import org.zalando.logbook.Logbook.ResponseWritingStage; +import org.zalando.logbook.Strategy; +import javax.annotation.Nullable; import javax.servlet.DispatcherType; import javax.servlet.FilterChain; import javax.servlet.ServletException; @@ -21,13 +24,19 @@ public final class LogbookFilter implements HttpFilter { private static final String STAGE = ResponseProcessingStage.class.getName(); private final Logbook logbook; + private final Strategy strategy; public LogbookFilter() { this(Logbook.create()); } public LogbookFilter(final Logbook logbook) { + this(logbook, null); + } + + public LogbookFilter(final Logbook logbook, @Nullable final Strategy strategy) { this.logbook = logbook; + this.strategy = strategy; } @Override @@ -55,10 +64,16 @@ private ResponseProcessingStage logRequest(final HttpServletRequest httpRequest, if (httpRequest.getDispatcherType() == DispatcherType.ASYNC) { return (ResponseProcessingStage) httpRequest.getAttribute(STAGE); } else { - final ResponseProcessingStage stage = logbook.process(request).write(); + final ResponseProcessingStage stage = process(request).write(); httpRequest.setAttribute(STAGE, stage); return stage; } } + private RequestWritingStage process(final HttpRequest request) throws IOException { + return strategy == null ? + logbook.process(request) : + logbook.process(request, strategy); + } + } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java index ffe99e8cc..ec15a744a 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java @@ -149,6 +149,7 @@ static String encode(final String s, final String charset) { @Override public ServletInputStream getInputStream() { + // TODO we need the ability to not buffer but still allow downstream filters/servlets to read the original request return new ServletInputStreamAdapter(new ByteArrayInputStream(body)); } diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java index 6a50871da..31e34038b 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/SecureLogbookFilter.java @@ -2,10 +2,8 @@ import org.apiguardian.api.API; import org.zalando.logbook.Logbook; -import org.zalando.logbook.Logbook.RequestWritingStage; -import org.zalando.logbook.Logbook.ResponseWritingStage; +import org.zalando.logbook.SecurityStrategy; -import javax.servlet.DispatcherType; import javax.servlet.FilterChain; import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; @@ -17,56 +15,21 @@ @API(status = STABLE) public final class SecureLogbookFilter implements HttpFilter { - private static final String STAGE = RequestWritingStage.class.getName(); - - private final Logbook logbook; + private final HttpFilter filter; public SecureLogbookFilter() { this(Logbook.create()); } public SecureLogbookFilter(final Logbook logbook) { - this.logbook = logbook; + this.filter = new LogbookFilter(logbook, new SecurityStrategy()); } @Override - public void doFilter(final HttpServletRequest httpRequest, final HttpServletResponse httpResponse, + public void doFilter(final HttpServletRequest request, final HttpServletResponse response, final FilterChain chain) throws ServletException, IOException { - final RemoteRequest request = new RemoteRequest(httpRequest); - final LocalResponse response = new LocalResponse(httpResponse, request.getProtocolVersion()); - - final RequestWritingStage writeRequest = processRequest(httpRequest, request); - - // effectively overriding the decision from the underlying strategy - request.withoutBody(); - - chain.doFilter(request, response); - - if (request.isAsyncStarted()) { - return; - } - - if (isUnauthorizedOrForbidden(response)) { - final ResponseWritingStage process = writeRequest.write().process(response); - response.getWriter().flush(); - process.write(); - } - } - - private RequestWritingStage processRequest(final HttpServletRequest httpRequest, final RemoteRequest request) throws IOException { - if (httpRequest.getDispatcherType() == DispatcherType.ASYNC) { - return (RequestWritingStage) httpRequest.getAttribute(STAGE); - } else { - final RequestWritingStage stage = logbook.process(request); - httpRequest.setAttribute(STAGE, stage); - return stage; - } - } - - private boolean isUnauthorizedOrForbidden(final HttpServletResponse response) { - final int status = response.getStatus(); - return status == 401 || status == 403; + filter.doFilter(request, response, chain); } } From 4c2ca264f9ee75982f8b7b2b967112f61ba129f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 21:43:10 +0100 Subject: [PATCH 12/13] Fixes #439 --- .../java/org/zalando/logbook/Mockbook.java | 1 - .../logbook/servlet/LocalResponse.java | 32 +++++++++++++------ .../logbook/servlet/RemoteRequest.java | 31 +++++++++++------- .../logbook/servlet/AsyncDispatchTest.java | 25 ++++++++++++--- .../logbook/servlet/ExampleController.java | 18 ++++++++--- .../servlet/MultiFilterSecurityTest.java | 10 ++++-- 6 files changed, 85 insertions(+), 32 deletions(-) diff --git a/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java b/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java index 4c96e3d62..dcd28977b 100644 --- a/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java +++ b/logbook-api/src/test/java/org/zalando/logbook/Mockbook.java @@ -3,7 +3,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import java.io.IOException; import java.util.function.Predicate; @AllArgsConstructor diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java index d96ef486d..1e4251300 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/LocalResponse.java @@ -25,7 +25,9 @@ final class LocalResponse extends HttpServletResponseWrapper implements HttpResp private final String protocolVersion; - private Tee tee; + private Tee body; + private Tee buffer; + private boolean used; // point of no return, once we exposed our stream, we need to buffer LocalResponse(final HttpServletResponse response, final String protocolVersion) { super(response); @@ -60,39 +62,51 @@ public Charset getCharset() { @Override public HttpResponse withBody() throws IOException { - if (tee == null) { - this.tee = new Tee(super.getOutputStream()); + if (body == null) { + bufferIfNecessary(); + this.body = buffer; } return this; } + private void bufferIfNecessary() throws IOException { + if (buffer == null) { + this.buffer = new Tee(super.getOutputStream()); + } + } + @Override public HttpResponse withoutBody() { - this.tee = null; + this.body = null; + if (!used) { + this.buffer = null; + } return this; } @Override public ServletOutputStream getOutputStream() throws IOException { - if (tee == null) { + if (buffer == null) { return super.getOutputStream(); } else { - return tee.getOutputStream(); + this.used = true; + return buffer.getOutputStream(); } } @Override public PrintWriter getWriter() throws IOException { - if (tee == null) { + if (buffer == null) { return super.getWriter(); } else { - return tee.getWriter(this::getCharset); + this.used = true; + return buffer.getWriter(this::getCharset); } } @Override public byte[] getBody() { - return tee == null ? new byte[0] : tee.getBytes(); + return body == null ? new byte[0] : body.getBytes(); } private static class Tee { diff --git a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java index ec15a744a..f86f0263a 100644 --- a/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java +++ b/logbook-servlet/src/main/java/org/zalando/logbook/servlet/RemoteRequest.java @@ -30,6 +30,7 @@ final class RemoteRequest extends HttpServletRequestWrapper implements HttpReque private final FormRequestMode formRequestMode = FormRequestMode.fromProperties(); private byte[] body; + private byte[] buffered; RemoteRequest(final HttpServletRequest request) { super(request); @@ -93,28 +94,35 @@ public Charset getCharset() { @Override public HttpRequest withBody() throws IOException { if (body == null) { + bufferIfNecessary(); + this.body = buffered; + } + + return this; + } + + private void bufferIfNecessary() throws IOException { + if (buffered == null) { if (isFormRequest()) { switch (formRequestMode) { case PARAMETER: - this.body = reconstructBodyFromParameters(); - return this; + this.buffered = reconstructBodyFromParameters(); + return; case OFF: - this.body = new byte[0]; - return this; + this.buffered = new byte[0]; + return; default: break; } } - this.body = ByteStreams.toByteArray(super.getInputStream()); + this.buffered = ByteStreams.toByteArray(super.getInputStream()); } - - return this; } @Override public HttpRequest withoutBody() { - this.body = new byte[0]; + this.body = null; return this; } @@ -148,9 +156,10 @@ static String encode(final String s, final String charset) { } @Override - public ServletInputStream getInputStream() { - // TODO we need the ability to not buffer but still allow downstream filters/servlets to read the original request - return new ServletInputStreamAdapter(new ByteArrayInputStream(body)); + public ServletInputStream getInputStream() throws IOException { + return buffered == null ? + super.getInputStream() : + new ServletInputStreamAdapter(new ByteArrayInputStream(buffered)); } @Override diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java index 4b5e8b2d3..dd7654260 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/AsyncDispatchTest.java @@ -5,6 +5,7 @@ import org.mockito.ArgumentCaptor; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.zalando.logbook.Correlation; import org.zalando.logbook.DefaultHttpLogFormatter; import org.zalando.logbook.DefaultSink; import org.zalando.logbook.HttpLogFormatter; @@ -13,6 +14,8 @@ import org.zalando.logbook.HttpRequest; import org.zalando.logbook.HttpResponse; import org.zalando.logbook.Logbook; +import org.zalando.logbook.Precorrelation; +import org.zalando.logbook.Sink; import org.zalando.logbook.Strategy; import javax.servlet.DispatcherType; @@ -52,14 +55,28 @@ final class AsyncDispatchTest { .strategy(new Strategy() { @Override public HttpRequest process(final HttpRequest request) throws IOException { - request.getBody(); - return request.withBody().withBody(); + return request.withBody().withBody().withoutBody().withBody(); + } + + @Override + public void write(final Precorrelation precorrelation, final HttpRequest request, + final Sink sink) throws IOException { + + request.withoutBody().withBody(); + sink.write(precorrelation, request); } @Override public HttpResponse process(final HttpRequest request, final HttpResponse response) throws IOException { - response.getBody(); - return response.withBody().withBody(); + return response.withBody().withBody().withoutBody().withBody(); + } + + @Override + public void write(final Correlation correlation, final HttpRequest request, + final HttpResponse response, final Sink sink) throws IOException { + + response.withoutBody().withBody(); + sink.write(correlation, request, response); } }) .sink(new DefaultSink(formatter, writer)) diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java index 83562f538..032b526f8 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/ExampleController.java @@ -4,6 +4,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -17,8 +18,10 @@ import java.util.Objects; import java.util.concurrent.Callable; +import static org.springframework.http.MediaType.TEXT_PLAIN_VALUE; + @RestController -@RequestMapping(value = "/api", produces = MediaType.APPLICATION_JSON_VALUE) +@RequestMapping(path = "/api", produces = MediaType.APPLICATION_JSON_VALUE) public class ExampleController { @RequestMapping("/sync") @@ -28,6 +31,11 @@ public ResponseEntity message() { return ResponseEntity.ok(message); } + @RequestMapping(path = "/echo", consumes = TEXT_PLAIN_VALUE, produces = TEXT_PLAIN_VALUE) + public ResponseEntity echo(@RequestBody final String message) { + return ResponseEntity.ok(message); + } + @RequestMapping("/async") public Callable> returnMessage() { return () -> { @@ -48,7 +56,7 @@ public void error() { throw new UnsupportedOperationException(); } - @RequestMapping(value = "/read-byte", produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(path = "/read-byte", produces = TEXT_PLAIN_VALUE) public void readByte(final HttpServletRequest request, final HttpServletResponse response) throws IOException { final ServletInputStream input = request.getInputStream(); @@ -63,7 +71,7 @@ public void readByte(final HttpServletRequest request, final HttpServletResponse } } - @RequestMapping(value = "/read-bytes", produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(path = "/read-bytes", produces = TEXT_PLAIN_VALUE) public void readBytes(final HttpServletRequest request, final HttpServletResponse response) throws IOException { final ServletInputStream input = request.getInputStream(); @@ -80,12 +88,12 @@ public void readBytes(final HttpServletRequest request, final HttpServletRespons } } - @RequestMapping(value = "/stream", produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(path = "/stream", produces = TEXT_PLAIN_VALUE) public void stream(final HttpServletRequest request, final HttpServletResponse response) throws IOException { ByteStreams.copy(request.getInputStream(), response.getOutputStream()); } - @RequestMapping(value = "/reader", produces = MediaType.TEXT_PLAIN_VALUE) + @RequestMapping(path = "/reader", produces = TEXT_PLAIN_VALUE) public void reader(final HttpServletRequest request, final HttpServletResponse response) throws IOException { try (final PrintWriter writer = response.getWriter()) { copy(request.getReader(), writer); diff --git a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java index b9359c78a..57a603959 100644 --- a/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java +++ b/logbook-servlet/src/test/java/org/zalando/logbook/servlet/MultiFilterSecurityTest.java @@ -27,6 +27,7 @@ import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import static org.mockito.hamcrest.MockitoHamcrest.argThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request; @@ -90,7 +91,6 @@ void shouldLogAuthorizedResponseOnce() throws Exception { @ParameterizedTest @ValueSource(ints = {401, 403}) - @SuppressWarnings("unchecked") void shouldFormatUnauthorizedRequestOnce(final int status) throws Exception { securityFilter.setStatus(status); @@ -101,7 +101,6 @@ void shouldFormatUnauthorizedRequestOnce(final int status) throws Exception { @ParameterizedTest @ValueSource(ints = {401, 403}) - @SuppressWarnings("unchecked") void shouldFormatUnauthorizedResponseOnce(final int status) throws Exception { securityFilter.setStatus(status); @@ -164,4 +163,11 @@ void shouldHandleUnauthorizedAsyncDispatchRequest() throws Exception { .andReturn())); } + @Test + void shouldEcho() throws Exception { + mvc.perform(get("/api/echo").content("Hello, world!")); + + verify(writer).write(any(Precorrelation.class), argThat(containsString("Hello, world!"))); + } + } From 4a2a71bdc2acfaac221bdb13347ec95ad525817a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Willi=20Sch=C3=B6nborn?= Date: Sun, 24 Feb 2019 22:27:41 +0100 Subject: [PATCH 13/13] Replaced join --- .../java/org/zalando/logbook/DefaultHttpLogFormatter.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java index ce087b2b7..8e60b81b6 100644 --- a/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java +++ b/logbook-core/src/main/java/org/zalando/logbook/DefaultHttpLogFormatter.java @@ -9,7 +9,6 @@ import java.util.List; import java.util.Map; -import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; import static java.util.stream.Collectors.toMap; import static org.apiguardian.api.API.Status.EXPERIMENTAL; @@ -175,7 +174,7 @@ private List formatHeaders(final HttpMessage message) { } private String formatHeaderValues(final Map.Entry> entry) { - return entry.getValue().stream().collect(joining(", ")); + return String.join(", ", entry.getValue()); } private String formatHeader(final Map.Entry entry) { @@ -193,7 +192,7 @@ private String formatHeader(final Map.Entry entry) { */ @API(status = EXPERIMENTAL) public String format(final List lines) { - return lines.stream().collect(joining("\n")); + return String.join("\n", lines); } }