diff --git a/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequest.java b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequest.java index ad7aca6..2d52838 100644 --- a/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequest.java +++ b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequest.java @@ -10,6 +10,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; @@ -117,7 +118,10 @@ public String toString() { public void addHeader(String header, String value) { List hv = new ArrayList<>(); hv.add(value); - this.getHeaders().put(header, hv); + this.request.getHeaders().add(header, value); } + + } + diff --git a/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequestLogFormatter.java b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequestLogFormatter.java new file mode 100644 index 0000000..76667f9 --- /dev/null +++ b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/ClientRequestLogFormatter.java @@ -0,0 +1,9 @@ +package be.wegenenverkeer.rxhttp; + +/** + * Created by Karel Maesen, Geovise BVBA on 2019-06-25. + */ +public interface ClientRequestLogFormatter { + + String toLogMessage(ClientRequest request); +} diff --git a/modules/java/src/main/java/be/wegenenverkeer/rxhttp/DefaultClientRequestLogFormatter.java b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/DefaultClientRequestLogFormatter.java new file mode 100644 index 0000000..2233a86 --- /dev/null +++ b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/DefaultClientRequestLogFormatter.java @@ -0,0 +1,59 @@ +package be.wegenenverkeer.rxhttp; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Created by Karel Maesen, Geovise BVBA on 2019-06-25. + */ +public class DefaultClientRequestLogFormatter implements ClientRequestLogFormatter { + + private final List headersToLog; + private final List formParamNamesToLog; + + @SuppressWarnings("unchecked") + public DefaultClientRequestLogFormatter() { + this(Collections.EMPTY_LIST, Collections.EMPTY_LIST); + } + + public DefaultClientRequestLogFormatter(List headersToLog, List formParamNamesToLog) { + this.headersToLog = headersToLog.stream().map( String::toLowerCase).collect(Collectors.toList()); + this.formParamNamesToLog = formParamNamesToLog.stream().map(String::toLowerCase).collect(Collectors.toList()); + } + + @Override + public String toLogMessage(ClientRequest request) { + StringBuilder sb = new StringBuilder(request.getMethod()) + .append(" ") + .append(request.getUrl()); + + List headerLogs = headersToLog.stream().map(header -> + header + ":" + String.join("; ", request.unwrap().getHeaders().getAll(header)) + ).collect(Collectors.toList()); + + if (!headerLogs.isEmpty()) { + sb.append("\theaders:"); + headerLogs.forEach(hl -> { + sb.append("\t").append(hl); + }); + } + + List paramList = request.unwrap().getFormParams().stream() + .filter(fparam -> formParamNamesToLog.contains(fparam.getName().toLowerCase())) + .map(fparam -> fparam.getName() + ":" + fparam.getValue()) + .collect(Collectors.toList()); + + if (!paramList.isEmpty()) { + sb.append("\tformParams:"); + paramList.forEach(pl -> { + sb.append("\t").append(pl); + }); + } + + return sb.toString(); + } + +} + diff --git a/modules/java/src/main/java/be/wegenenverkeer/rxhttp/RxHttpClient.java b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/RxHttpClient.java index a421f68..5af0387 100644 --- a/modules/java/src/main/java/be/wegenenverkeer/rxhttp/RxHttpClient.java +++ b/modules/java/src/main/java/be/wegenenverkeer/rxhttp/RxHttpClient.java @@ -1,13 +1,11 @@ package be.wegenenverkeer.rxhttp; -import be.wegenenverkeer.rxhttp.aws.AwsCredentialsProvider; -import be.wegenenverkeer.rxhttp.aws.AwsRegion; -import be.wegenenverkeer.rxhttp.aws.AwsService; -import be.wegenenverkeer.rxhttp.aws.AwsServiceEndPoint; -import be.wegenenverkeer.rxhttp.aws.AwsSignature4Signer; - +import be.wegenenverkeer.rxhttp.aws.*; import io.netty.handler.ssl.SslContext; -import org.asynchttpclient.*; +import org.asynchttpclient.AsyncCompletionHandler; +import org.asynchttpclient.AsyncHttpClient; +import org.asynchttpclient.DefaultAsyncHttpClientConfig; +import org.asynchttpclient.Response; import org.asynchttpclient.filter.RequestFilter; import org.asynchttpclient.filter.ThrottleRequestFilter; import org.slf4j.Logger; @@ -19,18 +17,10 @@ import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; +import java.util.*; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; import java.util.function.Function; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLContext; import static be.wegenenverkeer.rxhttp.CompleteResponseHandler.withCompleteResponse; import static be.wegenenverkeer.rxhttp.ServerResponse.wrap; @@ -47,18 +37,20 @@ public class RxHttpClient { final private AsyncHttpClient innerClient; final private RestClientConfig config; final private List requestSigners; + final private ClientRequestLogFormatter logFormatter; - protected RxHttpClient(AsyncHttpClient innerClient, RestClientConfig config, RequestSigner... requestSigners) { + protected RxHttpClient(AsyncHttpClient innerClient, RestClientConfig config, ClientRequestLogFormatter logFmt, RequestSigner... requestSigners) { this.innerClient = innerClient; this.config = config; this.requestSigners = Collections.unmodifiableList(Arrays.asList(requestSigners)); + this.logFormatter = logFmt; } /** * * Executes a request and returns an Observable for the complete response. */ public CompletableFuture execute(ClientRequest request, Function transformer) { - logger.info("Sending Request: " + request.toString()); + logger.info("Sending Request: " + toLogMessage(request)); //Note: we don't use Observable.toBlocking().toFuture() //because we need a CompletableFuture so that interop with Scala is possible final CompletableFuture future = new CompletableFuture<>(); @@ -105,7 +97,7 @@ public void onThrowable(Throwable t) { */ public Observable executeToCompletion(ClientRequest request, Function transformer) { return Observable.defer(() -> { - logger.info("Sending Request: " + request.toString()); + logger.info("Sending Request: " + toLogMessage(request)); AsyncSubject subject = AsyncSubject.create(); innerClient.executeRequest(request.unwrap(), new AsyncCompletionHandlerWrapper<>(subject, transformer)); return subject; @@ -199,6 +191,11 @@ public ClientRequestBuilder requestBuilder() { return new ClientRequestBuilder(this); } + protected String toLogMessage(ClientRequest request) { + return this.logFormatter.toLogMessage(request); + } + + AsyncHttpClient inner() { return this.innerClient; } @@ -277,6 +274,8 @@ static public class Builder { private AwsServiceEndPoint awsServiceEndPoint; private AwsCredentialsProvider awsCredentialsProvider; private List requestSigners = new LinkedList<>(); + private List headersToLog = new ArrayList<>(); + private ArrayList formParmsToLog = new ArrayList<>(); public RxHttpClient build() { addRestClientConfigsToConfigBuilder(); @@ -299,7 +298,9 @@ public RxHttpClient build() { requestSigners.add(new AwsSignature4Signer(this.awsServiceEndPoint, this.awsCredentialsProvider)); } - return new RxHttpClient(innerClient, rcConfig, requestSigners.toArray(new RequestSigner[0])); + ClientRequestLogFormatter logFmt = new DefaultClientRequestLogFormatter(headersToLog, formParmsToLog); + + return new RxHttpClient(innerClient, rcConfig, logFmt, requestSigners.toArray(new RequestSigner[0])); } /** @@ -420,6 +421,7 @@ public RxHttpClient.Builder setMaxConnections(int maxConnections) { * @param allowPoolingConnections true if connection can be pooled by a ChannelPool * @return a {@link RxHttpClient.Builder} */ + @Deprecated public RxHttpClient.Builder setAllowPoolingConnections(boolean allowPoolingConnections) { configBuilder.setKeepAlive(allowPoolingConnections); return this; @@ -750,6 +752,7 @@ public RxHttpClient.Builder setFollowRedirect(boolean followRedirect) { * @param disableUrlEncodingForBoundedRequests disables the url encoding if set to true * @return this Builder */ + @Deprecated public RxHttpClient.Builder setDisableUrlEncodingForBoundedRequests(boolean disableUrlEncodingForBoundedRequests) { configBuilder.setDisableUrlEncodingForBoundRequests(disableUrlEncodingForBoundedRequests); return this; @@ -855,6 +858,17 @@ public Builder setAwsCredentialsProvider(AwsCredentialsProvider provider) { this.awsCredentialsProvider = provider; return this; } + + public Builder logHeaders(List headerNames) { + this.headersToLog = new ArrayList<>(headerNames); + return this; + } + + public Builder logFormParams(List formParameterNames) { + this.formParmsToLog = new ArrayList<>(formParameterNames); + return this; + } + } static private class BuildValidation { diff --git a/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientMultipleRequests.java b/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientMultipleRequests.java index 463490f..0691ef3 100644 --- a/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientMultipleRequests.java +++ b/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientMultipleRequests.java @@ -63,7 +63,7 @@ public void demonstrateComposableObservable() throws InterruptedException { .setUrlRelativetoBase(contactUrl).build(); return client .executeToCompletion(followUp, ServerResponse::getResponseBody) - .finallyDo(() -> LOGGER.info("ContactUrl " + contactUrl + " retrieved")); + .doAfterTerminate(() -> LOGGER.info("ContactUrl " + contactUrl + " retrieved")); }; LOGGER.info("Creating Observable..."); diff --git a/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientTestChunkedResponse.java b/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientTestChunkedResponse.java index 960d26b..5d0fa99 100644 --- a/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientTestChunkedResponse.java +++ b/modules/java/src/test/java/be/wegenenverkeer/designtests/RxHttpClientTestChunkedResponse.java @@ -1,6 +1,7 @@ package be.wegenenverkeer.designtests; import be.wegenenverkeer.rxhttp.*; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; import rx.Observable; @@ -8,10 +9,13 @@ import java.util.ArrayList; import java.util.List; -import java.util.concurrent.*; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; /** * Tests that demonstrate processing a long and slow chunked response. diff --git a/modules/java/src/test/java/be/wegenenverkeer/designtests/UsingWireMock.java b/modules/java/src/test/java/be/wegenenverkeer/designtests/UsingWireMock.java index c10f2e0..0f85851 100644 --- a/modules/java/src/test/java/be/wegenenverkeer/designtests/UsingWireMock.java +++ b/modules/java/src/test/java/be/wegenenverkeer/designtests/UsingWireMock.java @@ -55,6 +55,7 @@ public void resetServer() { WireMock.resetToDefault(); } + @SuppressWarnings("unchecked") public List items(V... v) { return Arrays.asList(v); } diff --git a/modules/java/src/test/java/be/wegenenverkeer/rxhttp/TestLoggerConf.java b/modules/java/src/test/java/be/wegenenverkeer/rxhttp/TestLoggerConf.java new file mode 100644 index 0000000..e94c679 --- /dev/null +++ b/modules/java/src/test/java/be/wegenenverkeer/rxhttp/TestLoggerConf.java @@ -0,0 +1,105 @@ +package be.wegenenverkeer.rxhttp; + +import org.junit.Test; + +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; + +/** + * Created by Karel Maesen, Geovise BVBA on 2019-06-25. + */ +public class TestLoggerConf { + + private RxHttpClient client = null; + + @Test + public void testLoggerConfDefault(){ + client = new RxHttpClient.Builder().setBaseUrl("http://foo.com").build(); + try { + ClientRequest request = client.requestBuilder().setMethod("GET").setUrlRelativetoBase("/test").build(); + assertEquals("GET http://foo.com/test", client.toLogMessage(request)); + } finally { + client.close(); + } + } + + @Test + public void testLoggerConfWithHeader(){ + client = new RxHttpClient.Builder() + .setBaseUrl("http://foo.com") + .logHeaders(Arrays.asList("Test-Header")) + .build(); + try { + ClientRequest request = client + .requestBuilder() + .setMethod("GET") + .setUrlRelativetoBase("/test") + .setHeader("Test-Header", "testvalue") + .build(); + assertEquals("GET http://foo.com/test\theaders:\ttest-header:testvalue", client.toLogMessage(request)); + } finally { + client.close(); + } + } + + @Test + public void testLoggerConfWithHeaderIsCaseInsentivie(){ + client = new RxHttpClient.Builder() + .setBaseUrl("http://foo.com") + .logHeaders(Arrays.asList("Test-Header")) + .build(); + try { + ClientRequest request = client + .requestBuilder() + .setMethod("GET") + .setUrlRelativetoBase("/test") + .setHeader("test-Header", "testvalue") + .build(); + assertEquals("GET http://foo.com/test\theaders:\ttest-header:testvalue", client.toLogMessage(request)); + } finally { + client.close(); + } + } + + @Test + public void testLoggerConfWithFParamIsCaseInsensitive(){ + client = new RxHttpClient.Builder() + .setBaseUrl("http://foo.com") + .logFormParams(Arrays.asList("TestParam")) + .build(); + try { + ClientRequest request = client + .requestBuilder() + .setMethod("GET") + .setUrlRelativetoBase("/test") + .addFormParam("testparam", "testparamValue") + .build(); + assertEquals("GET http://foo.com/test\tformParams:\ttestparam:testparamValue", client.toLogMessage(request)); + } finally { + client.close(); + } + + } + + @Test + public void testLoggerConfWithFormParam(){ + client = new RxHttpClient.Builder() + .setBaseUrl("http://foo.com") + .logFormParams(Arrays.asList("TestParam")) + .build(); + try { + ClientRequest request = client + .requestBuilder() + .setMethod("GET") + .setUrlRelativetoBase("/test") + .addFormParam("TestParam", "testparamValue") + .build(); + assertEquals("GET http://foo.com/test\tformParams:\tTestParam:testparamValue", client.toLogMessage(request)); + } finally { + client.close(); + } + + } + +} diff --git a/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestClientRequest.java b/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestClientRequest.java new file mode 100644 index 0000000..13e18ac --- /dev/null +++ b/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestClientRequest.java @@ -0,0 +1,29 @@ +package be.wegenenverkeer.rxhttp.aws; + +import be.wegenenverkeer.rxhttp.ClientRequest; +import be.wegenenverkeer.rxhttp.RxHttpClient; +import org.junit.Test; + +import java.util.Collections; + +import static org.junit.Assert.assertEquals; + +/** + * Created by Karel Maesen, Geovise BVBA on 2019-06-24. + */ +public class TestClientRequest { + + private RxHttpClient client = new RxHttpClient + .Builder() + .setBaseUrl("http://foo.com") + .setMaxConnections(1) + .build(); + + @Test + public void testAddHeaderAfterBuild() { + ClientRequest request = client.requestBuilder().build(); + request.addHeader("Test-Header", "hasAValue"); + assertEquals(request.getHeaders().get("Test-Header"), Collections.singletonList("hasAValue")); + + } +} diff --git a/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestInstanceCredentialsProvider.java b/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestInstanceCredentialsProvider.java index 44a0c26..19bb65c 100644 --- a/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestInstanceCredentialsProvider.java +++ b/modules/java/src/test/java/be/wegenenverkeer/rxhttp/aws/TestInstanceCredentialsProvider.java @@ -5,7 +5,7 @@ import java.time.OffsetDateTime; import java.time.ZoneOffset; -import static junit.framework.Assert.assertEquals; +import static org.junit.Assert.assertEquals; /** * Created by Karel Maesen, Geovise BVBA on 07/02/17. diff --git a/modules/scala/src/test/scala/be/wegenenverkeer/designtests/RxHTTPClientGetSpecs.scala b/modules/scala/src/test/scala/be/wegenenverkeer/designtests/RxHTTPClientGetSpecs.scala index 4fb9d39..c2353dc 100644 --- a/modules/scala/src/test/scala/be/wegenenverkeer/designtests/RxHTTPClientGetSpecs.scala +++ b/modules/scala/src/test/scala/be/wegenenverkeer/designtests/RxHTTPClientGetSpecs.scala @@ -54,7 +54,7 @@ class RxHTTPClientGetSpecs extends Specification { } - "return scala Future with execute" in new UsingMockServer with NoTimeConversions { + "return scala Future with execute" in new UsingMockServer { import scala.concurrent.duration._ @@ -82,7 +82,7 @@ class RxHTTPClientGetSpecs extends Specification { response must_== expectBody } - "return scala Future with executeCompletely" in new UsingMockServer with NoTimeConversions { + "return scala Future with executeCompletely" in new UsingMockServer { import scala.concurrent.duration._ diff --git a/project/BuildSettings.scala b/project/BuildSettings.scala index e89b02b..3d83297 100644 --- a/project/BuildSettings.scala +++ b/project/BuildSettings.scala @@ -10,7 +10,7 @@ object BuildSettings { val Organization = "be.wegenenverkeer" - val Version = "1.0.0" + val Version = "1.1.0" val ScalaVersion = "2.12.8" val ScalaBuildOptions = Seq("-unchecked", "-deprecation", "-feature", @@ -70,6 +70,7 @@ object BuildSettings { lazy val extraJavaSettings = Seq( crossPaths := false, autoScalaLibrary := false, + //javacOptions ++= Seq("-Xlint:deprecation"), testOptions += Tests.Argument(TestFrameworks.JUnit, "-q", "-v") )