From 16c16c4ab2196f8c5ebae691c72460d8c0b5f72a Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 10 Mar 2022 16:50:24 +0100 Subject: [PATCH 01/14] gzip encoding for subscribing --- fahrschein-http-simple/pom.xml | 23 +-- .../http/simple/SimpleBufferingRequest.java | 3 + .../http/simple/SimpleRequestFactory.java | 3 +- .../http/simple/SimpleResponse.java | 4 + .../http/simple/SimpleRequestFactoryTest.java | 182 ++++++++++++++++++ 5 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java diff --git a/fahrschein-http-simple/pom.xml b/fahrschein-http-simple/pom.xml index 5a7001a9..ea99cdf5 100644 --- a/fahrschein-http-simple/pom.xml +++ b/fahrschein-http-simple/pom.xml @@ -14,22 +14,23 @@ fahrschein-http-simple Fahrschein HTTP Client using HttpURLConnection - - - - maven-surefire-plugin - - false - - - - - org.zalando fahrschein-http-api ${project.version} + + junit + junit + ${version.junit} + test + + + org.mockito + mockito-core + ${version.mockito} + test + diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java index 46747195..430549ab 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java @@ -66,6 +66,9 @@ private Response executeInternal() throws IOException { } } } + if (connection.getRequestProperty("Accept-Encoding") == null) { + connection.setRequestProperty("Accept-Encoding", "gzip"); + } if (this.connection.getDoOutput()) { this.connection.setFixedLengthStreamingMode(size); diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java index b152f2fd..0de999fa 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java @@ -58,11 +58,12 @@ public Request createRequest(URI uri, String method) throws IOException { * @param url the URL to open a connection to * @return the opened connection * @throws IOException in case of I/O errors + * @throws IllegalArgumentException in case {{@link java.net.URL#openConnection()}} does not lead to a HttpURLConnection */ private HttpURLConnection openConnection(URL url) throws IOException { URLConnection urlConnection = url.openConnection(); if (!(urlConnection instanceof HttpURLConnection)) { - throw new IllegalStateException("Connection should be an HttpURLConnection"); + throw new IllegalArgumentException("Connection should be an HttpURLConnection"); } return (HttpURLConnection) urlConnection; } diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java index e88fe9b4..21516b84 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java @@ -7,6 +7,7 @@ import java.io.IOException; import java.io.InputStream; import java.net.HttpURLConnection; +import java.util.zip.GZIPInputStream; /** * {@link Response} implementation that uses standard JDK facilities. @@ -65,6 +66,9 @@ public InputStream getBody() throws IOException { if (this.responseStream == null) { final InputStream errorStream = connection.getErrorStream(); this.responseStream = (errorStream != null ? errorStream : connection.getInputStream()); + if (this.getHeaders().get("Content-Encoding").contains("gzip")) { + this.responseStream = new GZIPInputStream(this.responseStream); + } } return this.responseStream; } diff --git a/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java b/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java new file mode 100644 index 00000000..1f90a6d6 --- /dev/null +++ b/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java @@ -0,0 +1,182 @@ +package org.zalando.fahrschein.http.simple; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.zalando.fahrschein.http.api.ContentType; +import org.zalando.fahrschein.http.api.Request; +import org.zalando.fahrschein.http.api.Response; + +import java.io.*; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.zip.GZIPOutputStream; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class SimpleRequestFactoryTest { + + static HttpServer server; + static URI serverAddress; + + @BeforeClass + public static void startServer() throws IOException { + server = HttpServer.create(new InetSocketAddress("localhost", 0), 1); + serverAddress = URI.create("http://localhost:" + server.getAddress().getPort()); + server.setExecutor(Executors.newSingleThreadExecutor()); + server.start(); + } + + @Captor + public ArgumentCaptor exchangeCaptor; + + @Test + public void testGetRequest() throws IOException { + // given + String expectedResponse = "{}"; + HttpHandler spy = Mockito.spy(new SimpleResponseContentHandler(expectedResponse)); + server.createContext("/get", spy); + + // when + SimpleRequestFactory f = new SimpleRequestFactory(); + Request r = f.createRequest(serverAddress.resolve("/get"), "GET"); + Response executed = r.execute(); + String actualResponse = readResponse(executed); + + // then + assertEquals(serverAddress.resolve("/get"), r.getURI()); + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor. getValue(); + assertEquals("GET", capturedArgument.getRequestMethod()); + assertEquals(200, executed.getStatusCode()); + assertEquals("OK", executed.getStatusText()); + assertEquals(expectedResponse, actualResponse); + } + + @Test + public void testPostRequest() throws IOException { + // given + String expectedResponse = "{}"; + HttpHandler spy = Mockito.spy(new SimpleResponseContentHandler(expectedResponse)); + server.createContext("/post", spy); + + // when + SimpleRequestFactory f = new SimpleRequestFactory(); + Request r = f.createRequest(serverAddress.resolve("/post"), "POST"); + r.getHeaders().setContentType(ContentType.APPLICATION_JSON); + try (final OutputStream body = r.getBody()) { + body.write("{}".getBytes()); + } + Response executed = r.execute(); + String actualResponse = readResponse(executed); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertEquals("POST", capturedArgument.getRequestMethod()); + assertEquals(URI.create("/post"), capturedArgument.getRequestURI()); + assertEquals(expectedResponse, actualResponse); + } + + @Test + public void testGzippedResponse() throws IOException { + // given + String expectedResponse = "{}"; + HttpHandler spy = Mockito.spy(new GzippedResponseContentHandler(expectedResponse)); + server.createContext("/gzipped", spy); + + // when + SimpleRequestFactory f = new SimpleRequestFactory(); + Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); + Response executed = r.execute(); + String actualResponse = readResponse(executed); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertThat("accept-encoding header", capturedArgument.getRequestHeaders().get("accept-encoding"), equalTo(List.of("gzip"))); + assertEquals(URI.create("/gzipped"), capturedArgument.getRequestURI()); + assertEquals(expectedResponse, actualResponse); + } + + @Test(expected = SocketTimeoutException.class) + public void testTimeout() throws IOException { + // given + server.createContext("/timeout", exchange -> { + try { + Thread.sleep(10l); + exchange.sendResponseHeaders(201, 0); + } catch (InterruptedException e) { } + }); + + // when + SimpleRequestFactory f = new SimpleRequestFactory(); + f.setReadTimeout(1); + Request r = f.createRequest(serverAddress.resolve("/timeout"), "GET"); + r.execute(); + } + + private String readResponse(Response executed) throws IOException { + String res = new BufferedReader( + new InputStreamReader(executed.getBody(), StandardCharsets.UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + executed.close(); + return res; + } + + private static class SimpleResponseContentHandler implements HttpHandler { + + private final byte[] rawResponse; + + SimpleResponseContentHandler(String response) { + this.rawResponse = response.getBytes(StandardCharsets.UTF_8); + } + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.sendResponseHeaders(200, rawResponse.length); + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(rawResponse); + responseBody.close(); + } + } + + private static class GzippedResponseContentHandler implements HttpHandler { + + private final byte[] rawResponse; + + GzippedResponseContentHandler(String response) throws IOException { + byte[] stringResponse = response.getBytes(StandardCharsets.UTF_8); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + GZIPOutputStream zipStream = new GZIPOutputStream(byteStream); + zipStream.write(stringResponse); + zipStream.close(); + this.rawResponse = byteStream.toByteArray(); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().set("Content-Encoding", "gzip"); + exchange.sendResponseHeaders(200, rawResponse.length); + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(rawResponse); + responseBody.flush(); + responseBody.close(); + } + } +} From 669b21c3f8a9d6d23322a74b5df8f478da865d11 Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 10 Mar 2022 17:49:57 +0100 Subject: [PATCH 02/14] adds full content-compression support to all client adapters Support gzip compression for publishing Add references to spring documentation, code fix deletes an unrelated change revert draining (to avoid mixing of concerns) Adds API docs to RequestFactory interface --- fahrschein-http-apache/pom.xml | 12 ++ .../http/apache/HttpComponentsRequest.java | 12 +- .../apache/HttpComponentsRequestFactory.java | 8 +- .../HttpComponentsRequestFactoryTest.java | 196 +++++++++++++++++ .../fahrschein/http/api/RequestFactory.java | 12 ++ .../http/simple/SimpleBufferingRequest.java | 17 +- .../http/simple/SimpleRequestFactory.java | 13 +- .../http/simple/SimpleResponse.java | 4 +- .../http/simple/SimpleRequestFactoryTest.java | 107 +++++++--- fahrschein-http-spring/pom.xml | 12 ++ .../http/spring/RequestAdapter.java | 12 +- .../http/spring/SpringRequestFactory.java | 13 +- .../http/spring/SpringRequestFactoryTest.java | 201 ++++++++++++++++++ .../fahrschein/AuthorizedRequestFactory.java | 5 + .../ProblemHandlingRequestFactory.java | 5 + .../org/zalando/fahrschein/MockServer.java | 5 + 16 files changed, 600 insertions(+), 34 deletions(-) create mode 100644 fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java create mode 100644 fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java diff --git a/fahrschein-http-apache/pom.xml b/fahrschein-http-apache/pom.xml index 68848e87..d1bf9c06 100644 --- a/fahrschein-http-apache/pom.xml +++ b/fahrschein-http-apache/pom.xml @@ -36,6 +36,18 @@ httpclient 4.5.13 + + org.mockito + mockito-core + ${version.mockito} + test + + + junit + junit + ${version.junit} + test + \ No newline at end of file diff --git a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java index 3ede52b4..c00a46af 100644 --- a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java +++ b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java @@ -4,6 +4,7 @@ import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.GzipCompressingEntity; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.protocol.HTTP; @@ -17,6 +18,7 @@ import java.io.OutputStream; import java.net.URI; import java.util.List; +import java.util.zip.GZIPOutputStream; /** * {@link Request} implementation based on Apache HttpComponents HttpClient. @@ -33,14 +35,16 @@ final class HttpComponentsRequest implements Request { private final HttpClient httpClient; private final HttpUriRequest httpRequest; + private final Boolean contentCompression; private final Headers headers; private ByteArrayOutputStream bufferedOutput; private boolean executed; - HttpComponentsRequest(HttpClient client, HttpUriRequest request) { + HttpComponentsRequest(HttpClient client, HttpUriRequest request, Boolean contentCompression) { this.httpClient = client; this.httpRequest = request; + this.contentCompression = contentCompression; this.headers = new HeadersImpl(); } @@ -74,6 +78,9 @@ private Response executeInternal(Headers headers) throws IOException { HttpEntityEnclosingRequest entityEnclosingRequest = (HttpEntityEnclosingRequest) this.httpRequest; HttpEntity requestEntity = new ByteArrayEntity(bytes); entityEnclosingRequest.setEntity(requestEntity); + if(this.contentCompression && this.httpRequest.getFirstHeader("Content-Encoding") == null) { + this.httpRequest.setHeader("Content-Encoding", "gzip"); + } } final HttpResponse httpResponse = this.httpClient.execute(this.httpRequest); @@ -93,6 +100,9 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); + if (this.contentCompression) { + return new GZIPOutputStream(this.bufferedOutput); + } } return this.bufferedOutput; } diff --git a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java index 978c5c22..25efb36c 100644 --- a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java +++ b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java @@ -35,6 +35,7 @@ public class HttpComponentsRequestFactory implements RequestFactory { private final HttpClient httpClient; + private boolean contentCompression = true; /** * Create a new instance of the {@code HttpComponentsRequestFactory} @@ -48,11 +49,16 @@ public HttpComponentsRequestFactory(HttpClient httpClient) { this.httpClient = httpClient; } + @Override + public void disableContentCompression() { + this.contentCompression = false; + } + @Override public Request createRequest(URI uri, String httpMethod) throws IOException { final HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); - return new HttpComponentsRequest(httpClient, httpRequest); + return new HttpComponentsRequest(httpClient, httpRequest, contentCompression); } /** diff --git a/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java b/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java new file mode 100644 index 00000000..c32456af --- /dev/null +++ b/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java @@ -0,0 +1,196 @@ +package org.zalando.fahrschein.http.apache; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.impl.client.CloseableHttpClient; +import org.apache.http.impl.client.HttpClients; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.zalando.fahrschein.http.api.ContentType; +import org.zalando.fahrschein.http.api.Request; +import org.zalando.fahrschein.http.api.RequestFactory; +import org.zalando.fahrschein.http.api.Response; + +import java.io.*; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class HttpComponentsRequestFactoryTest { + + static HttpServer server; + static URI serverAddress; + + @BeforeClass + public static void startServer() throws IOException { + server = HttpServer.create(new InetSocketAddress("localhost", 0), 1); + serverAddress = URI.create("http://localhost:" + server.getAddress().getPort()); + ExecutorService executor = Executors.newSingleThreadExecutor(); + server.setExecutor(executor); + server.start(); + } + + @Captor + public ArgumentCaptor exchangeCaptor; + + @Test + public void testGzippedResponseBody() throws IOException { + // given + String expectedResponse = "{}"; + HttpHandler spy = Mockito.spy(new GzippedResponseContentHandler(expectedResponse)); + server.createContext("/gzipped", spy); + + // when + final RequestFactory f = defaultRequestFactory(); + f.disableContentCompression(); + Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); + Response executed = r.execute(); + String actualResponse = readStream(executed.getBody()); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertThat("accept-encoding header", capturedArgument.getRequestHeaders().get("accept-encoding"), equalTo(Arrays.asList("gzip,deflate"))); + assertThat("no content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), nullValue()); + assertEquals(URI.create("/gzipped"), capturedArgument.getRequestURI()); + assertEquals(expectedResponse, actualResponse); + } + + @Test(expected = SocketTimeoutException.class) + public void testTimeout() throws IOException { + // given + server.createContext("/timeout", exchange -> { + try { + Thread.sleep(10l); + exchange.sendResponseHeaders(201, 0); + } catch (InterruptedException e) { } + }); + + // when + RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1).build(); + final CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).build(); + RequestFactory f = new HttpComponentsRequestFactory(httpClient); + Request r = f.createRequest(serverAddress.resolve("/timeout"), "GET"); + r.execute(); + } + + @Test + public void testGzippedRequestBody() throws IOException { + // given + String requestBody = "{}"; + String responseBody = "{}"; + SimpleRequestResponseContentHandler spy = Mockito.spy(new SimpleRequestResponseContentHandler(responseBody)); + server.createContext("/gzipped-post", spy); + + // when + CloseableHttpClient c = HttpClients.createDefault(); + HttpComponentsRequestFactory f = new HttpComponentsRequestFactory(c); + + Request r = f.createRequest(serverAddress.resolve("/gzipped-post"), "POST"); + r.getHeaders().setContentType(ContentType.APPLICATION_JSON); + try (final OutputStream body = r.getBody()) { + body.write(requestBody.getBytes()); + } + Response executed = r.execute(); + String actualResponse = readStream(executed.getBody()); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertEquals("POST", capturedArgument.getRequestMethod()); + assertEquals(URI.create("/gzipped-post"), capturedArgument.getRequestURI()); + assertThat("content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), equalTo(Arrays.asList("gzip"))); + assertEquals(requestBody, spy.getRequestBody()); + assertEquals(responseBody, actualResponse); + } + + private RequestFactory defaultRequestFactory() { + return new HttpComponentsRequestFactory(HttpClients.createDefault()); + } + + static String readStream(InputStream stream) throws IOException { + String res = new BufferedReader( + new InputStreamReader(stream, UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + stream.close(); + return res; + } + + private static class SimpleRequestResponseContentHandler implements HttpHandler { + + private String requestBody; + private final String responseBody; + + SimpleRequestResponseContentHandler(String responseBody) { + this.responseBody = responseBody; + } + + public String getRequestBody() { + return requestBody; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + + try { + if (exchange.getRequestHeaders().containsKey("Content-Encoding") && exchange.getRequestHeaders().get("Content-Encoding").contains("gzip")) { + requestBody = readStream(new GZIPInputStream(exchange.getRequestBody())); + } else { + requestBody = readStream(exchange.getRequestBody()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + byte[] bytes = responseBody.getBytes(UTF_8); + exchange.sendResponseHeaders(200, bytes.length); + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(bytes); + responseBody.close(); + } + } + + private static class GzippedResponseContentHandler implements HttpHandler { + + private final byte[] rawResponse; + + GzippedResponseContentHandler(String response) throws IOException { + byte[] stringResponse = response.getBytes(UTF_8); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + GZIPOutputStream zipStream = new GZIPOutputStream(byteStream); + zipStream.write(stringResponse); + zipStream.close(); + this.rawResponse = byteStream.toByteArray(); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().set("Content-Encoding", "gzip"); + exchange.sendResponseHeaders(200, rawResponse.length); + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(rawResponse); + responseBody.close(); + } + } +} diff --git a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java index 6c3ddb2d..9cc12759 100644 --- a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java +++ b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java @@ -5,6 +5,18 @@ public interface RequestFactory { + /** + * By default, POST-content is gzip-compressed. This disables content compression. + */ + void disableContentCompression(); + + /** + * Creates a new request using the underlying RequestFactory implementation. + * @param uri request target URI + * @param method request method (GET, POST, ...) + * @return the request + * @throws IOException in case of I/O issues while trying to create the request. + */ Request createRequest(URI uri, String method) throws IOException; } diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java index 430549ab..3928c247 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java @@ -12,11 +12,15 @@ import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.util.zip.GZIPOutputStream; /** * {@link Request} implementation that uses standard JDK facilities to * execute buffered requests. Created via the {@link SimpleRequestFactory}. * + * See original + * code from Spring Framework. + * * @author Arjen Poutsma * @author Juergen Hoeller * @author Joern Horstmann @@ -26,12 +30,14 @@ final class SimpleBufferingRequest implements Request { private final HttpURLConnection connection; private final Headers headers; + private final Boolean contentCompression; private ByteArrayOutputStream bufferedOutput; private boolean executed; - SimpleBufferingRequest(HttpURLConnection connection) { + SimpleBufferingRequest(HttpURLConnection connection, Boolean contentCompression) { this.connection = connection; this.headers = new HeadersImpl(); + this.contentCompression = contentCompression; } @Override @@ -52,7 +58,6 @@ private Response executeInternal() throws IOException { final int size = this.bufferedOutput != null ? this.bufferedOutput.size() : 0; final long contentLength = this.headers.getContentLength(); - if (contentLength >= 0 && contentLength != size) { throw new IllegalStateException("Invalid Content-Length header [" + contentLength + "], request size is [" + size + "]"); } @@ -66,12 +71,17 @@ private Response executeInternal() throws IOException { } } } + + // allow gzip-compression from server response if (connection.getRequestProperty("Accept-Encoding") == null) { connection.setRequestProperty("Accept-Encoding", "gzip"); } if (this.connection.getDoOutput()) { this.connection.setFixedLengthStreamingMode(size); + if(this.contentCompression && this.connection.getRequestProperty("Content-Encoding") == null) { + connection.setRequestProperty("Content-Encoding", "gzip"); + } } this.connection.connect(); @@ -100,6 +110,9 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); + if (this.contentCompression) { + return new GZIPOutputStream(this.bufferedOutput); + } } return this.bufferedOutput; } diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java index 0de999fa..d0ba0f28 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java @@ -12,6 +12,9 @@ /** * {@link RequestFactory} implementation that uses standard JDK facilities. * + * See original + * code from Spring Framework + * * @author Arjen Poutsma * @author Juergen Hoeller * @author Joern Horstmann @@ -21,6 +24,7 @@ public class SimpleRequestFactory implements RequestFactory { private int connectTimeout = -1; private int readTimeout = -1; + private boolean contentCompression = true; /** * Set the underlying URLConnection's connect timeout (in milliseconds). @@ -44,18 +48,23 @@ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } + @Override + public void disableContentCompression() { + this.contentCompression = false; + } + @Override public Request createRequest(URI uri, String method) throws IOException { HttpURLConnection connection = openConnection(uri.toURL()); prepareConnection(connection, method); - return new SimpleBufferingRequest(connection); + return new SimpleBufferingRequest(connection, contentCompression); } /** * Opens and returns a connection to the given URL. * - * @param url the URL to open a connection to + * @param url the URL to open a connection to * @return the opened connection * @throws IOException in case of I/O errors * @throws IllegalArgumentException in case {{@link java.net.URL#openConnection()}} does not lead to a HttpURLConnection diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java index 21516b84..49d95a79 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleResponse.java @@ -13,6 +13,9 @@ * {@link Response} implementation that uses standard JDK facilities. * Obtained via {@link SimpleBufferingRequest#execute()}. * + * See original + * code from Spring Framework. + * * @author Arjen Poutsma * @author Brian Clozel * @author Joern Horstmann @@ -84,5 +87,4 @@ public void close() { } } } - } diff --git a/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java b/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java index 1f90a6d6..61b675ee 100644 --- a/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java +++ b/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java @@ -12,19 +12,22 @@ import org.mockito.junit.MockitoJUnitRunner; import org.zalando.fahrschein.http.api.ContentType; import org.zalando.fahrschein.http.api.Request; +import org.zalando.fahrschein.http.api.RequestFactory; import org.zalando.fahrschein.http.api.Response; import java.io.*; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import java.net.URI; -import java.nio.charset.StandardCharsets; -import java.util.List; +import java.util.Arrays; import java.util.concurrent.Executors; import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import static java.nio.charset.StandardCharsets.UTF_8; import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -49,14 +52,15 @@ public static void startServer() throws IOException { public void testGetRequest() throws IOException { // given String expectedResponse = "{}"; - HttpHandler spy = Mockito.spy(new SimpleResponseContentHandler(expectedResponse)); + HttpHandler spy = Mockito.spy(new SimpleRequestResponseContentHandler(expectedResponse)); server.createContext("/get", spy); // when - SimpleRequestFactory f = new SimpleRequestFactory(); + RequestFactory f = getRequestFactory(); + f.disableContentCompression(); Request r = f.createRequest(serverAddress.resolve("/get"), "GET"); Response executed = r.execute(); - String actualResponse = readResponse(executed); + String actualResponse = readStream(executed.getBody()); // then assertEquals(serverAddress.resolve("/get"), r.getURI()); @@ -71,45 +75,51 @@ public void testGetRequest() throws IOException { @Test public void testPostRequest() throws IOException { // given - String expectedResponse = "{}"; - HttpHandler spy = Mockito.spy(new SimpleResponseContentHandler(expectedResponse)); + String requestBody = "{}"; + String responseBody = "{}"; + SimpleRequestResponseContentHandler spy = Mockito.spy(new SimpleRequestResponseContentHandler(responseBody)); server.createContext("/post", spy); // when SimpleRequestFactory f = new SimpleRequestFactory(); + f.disableContentCompression(); Request r = f.createRequest(serverAddress.resolve("/post"), "POST"); r.getHeaders().setContentType(ContentType.APPLICATION_JSON); try (final OutputStream body = r.getBody()) { - body.write("{}".getBytes()); + body.write(requestBody.getBytes()); } Response executed = r.execute(); - String actualResponse = readResponse(executed); + String actualResponse = readStream(executed.getBody()); // then Mockito.verify(spy).handle(exchangeCaptor.capture()); HttpExchange capturedArgument = exchangeCaptor.getValue(); assertEquals("POST", capturedArgument.getRequestMethod()); + assertThat("no content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), nullValue()); assertEquals(URI.create("/post"), capturedArgument.getRequestURI()); - assertEquals(expectedResponse, actualResponse); + assertEquals(requestBody, spy.getRequestBody()); + assertEquals(responseBody, actualResponse); } @Test - public void testGzippedResponse() throws IOException { + public void testGzippedResponseBody() throws IOException { // given String expectedResponse = "{}"; HttpHandler spy = Mockito.spy(new GzippedResponseContentHandler(expectedResponse)); server.createContext("/gzipped", spy); // when - SimpleRequestFactory f = new SimpleRequestFactory(); + RequestFactory f = getRequestFactory(); + f.disableContentCompression(); Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); Response executed = r.execute(); - String actualResponse = readResponse(executed); + String actualResponse = readStream(executed.getBody()); // then Mockito.verify(spy).handle(exchangeCaptor.capture()); HttpExchange capturedArgument = exchangeCaptor.getValue(); - assertThat("accept-encoding header", capturedArgument.getRequestHeaders().get("accept-encoding"), equalTo(List.of("gzip"))); + assertThat("accept-encoding header", capturedArgument.getRequestHeaders().get("accept-encoding"), equalTo(Arrays.asList("gzip"))); + assertThat("no content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), nullValue()); assertEquals(URI.create("/gzipped"), capturedArgument.getRequestURI()); assertEquals(expectedResponse, actualResponse); } @@ -131,27 +141,74 @@ public void testTimeout() throws IOException { r.execute(); } - private String readResponse(Response executed) throws IOException { + @Test + public void testGzippedRequestBody() throws IOException { + // given + String requestBody = "{}"; + String responseBody = "{}"; + SimpleRequestResponseContentHandler spy = Mockito.spy(new SimpleRequestResponseContentHandler(responseBody)); + server.createContext("/gzipped-post", spy); + + // when + RequestFactory f = getRequestFactory(); + Request r = f.createRequest(serverAddress.resolve("/gzipped-post"), "POST"); + r.getHeaders().setContentType(ContentType.APPLICATION_JSON); + try (final OutputStream body = r.getBody()) { + body.write("{}".getBytes()); + } + Response executed = r.execute(); + String actualResponse = readStream(executed.getBody()); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertEquals("POST", capturedArgument.getRequestMethod()); + assertEquals(URI.create("/gzipped-post"), capturedArgument.getRequestURI()); + assertThat("content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), equalTo(Arrays.asList("gzip"))); + assertEquals(requestBody, spy.getRequestBody()); + assertEquals(responseBody, actualResponse); + } + + private RequestFactory getRequestFactory() { + return new SimpleRequestFactory(); + } + + static String readStream(InputStream stream) throws IOException { String res = new BufferedReader( - new InputStreamReader(executed.getBody(), StandardCharsets.UTF_8)) + new InputStreamReader(stream, UTF_8)) .lines() .collect(Collectors.joining("\n")); - executed.close(); + stream.close(); return res; } - private static class SimpleResponseContentHandler implements HttpHandler { + private static class SimpleRequestResponseContentHandler implements HttpHandler { - private final byte[] rawResponse; + private String requestBody; + private final String responseBody; + + SimpleRequestResponseContentHandler(String responseBody) { + this.responseBody = responseBody; + } - SimpleResponseContentHandler(String response) { - this.rawResponse = response.getBytes(StandardCharsets.UTF_8); + public String getRequestBody() { + return requestBody; } + @Override public void handle(HttpExchange exchange) throws IOException { - exchange.sendResponseHeaders(200, rawResponse.length); + + if (exchange.getRequestHeaders().containsKey("Content-Encoding") && exchange.getRequestHeaders().get("Content-Encoding").contains("gzip")) { + requestBody = readStream(new GZIPInputStream(exchange.getRequestBody())); + } else { + requestBody = readStream(exchange.getRequestBody()); + } + + byte[] bytes = responseBody.getBytes(UTF_8); + exchange.sendResponseHeaders(200, bytes.length); OutputStream responseBody = exchange.getResponseBody(); - responseBody.write(rawResponse); + responseBody.write(bytes); + responseBody.flush(); responseBody.close(); } } @@ -161,7 +218,7 @@ private static class GzippedResponseContentHandler implements HttpHandler { private final byte[] rawResponse; GzippedResponseContentHandler(String response) throws IOException { - byte[] stringResponse = response.getBytes(StandardCharsets.UTF_8); + byte[] stringResponse = response.getBytes(UTF_8); ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); GZIPOutputStream zipStream = new GZIPOutputStream(byteStream); zipStream.write(stringResponse); @@ -175,8 +232,8 @@ public void handle(HttpExchange exchange) throws IOException { exchange.sendResponseHeaders(200, rawResponse.length); OutputStream responseBody = exchange.getResponseBody(); responseBody.write(rawResponse); - responseBody.flush(); responseBody.close(); } } + } diff --git a/fahrschein-http-spring/pom.xml b/fahrschein-http-spring/pom.xml index 22be58b7..4b5d85ba 100644 --- a/fahrschein-http-spring/pom.xml +++ b/fahrschein-http-spring/pom.xml @@ -62,6 +62,18 @@ ${version.junit} test + + org.mockito + mockito-core + ${version.mockito} + test + + + com.squareup.okhttp3 + okhttp + 4.9.3 + test + \ No newline at end of file diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java index 81027e5e..dffb4446 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java @@ -8,12 +8,19 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URI; +import java.util.zip.GZIPOutputStream; class RequestAdapter implements Request { private final ClientHttpRequest clientHttpRequest; + private final boolean contentCompression; - RequestAdapter(ClientHttpRequest clientHttpRequest) { + RequestAdapter(ClientHttpRequest clientHttpRequest, Boolean contentCompression) { this.clientHttpRequest = clientHttpRequest; + this.contentCompression = contentCompression; + + if (contentCompression) { + clientHttpRequest.getHeaders().set("Content-Encoding", "gzip"); + } } @Override @@ -33,6 +40,9 @@ public Headers getHeaders() { @Override public OutputStream getBody() throws IOException { + if (this.contentCompression) { + return new GZIPOutputStream(clientHttpRequest.getBody()); + } return clientHttpRequest.getBody(); } diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java index 82cb0de0..625324d2 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java @@ -10,13 +10,24 @@ public class SpringRequestFactory implements RequestFactory { private final ClientHttpRequestFactory clientRequestFactory; + private Boolean contentCompression; public SpringRequestFactory(ClientHttpRequestFactory clientRequestFactory) { + this(clientRequestFactory, true); + } + + public SpringRequestFactory(ClientHttpRequestFactory clientRequestFactory, Boolean contentCompression) { this.clientRequestFactory = clientRequestFactory; + this.contentCompression = contentCompression; + } + + @Override + public void disableContentCompression() { + this.contentCompression = false; } @Override public Request createRequest(URI uri, String method) throws IOException { - return new RequestAdapter(clientRequestFactory.createRequest(uri, HttpMethod.valueOf(method))); + return new RequestAdapter(clientRequestFactory.createRequest(uri, HttpMethod.valueOf(method)), contentCompression); } } diff --git a/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java b/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java new file mode 100644 index 00000000..043160f0 --- /dev/null +++ b/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java @@ -0,0 +1,201 @@ +package org.zalando.fahrschein.http.spring; + +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import okhttp3.OkHttpClient; +import org.jetbrains.annotations.NotNull; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.zalando.fahrschein.http.api.ContentType; +import org.zalando.fahrschein.http.api.Request; +import org.zalando.fahrschein.http.api.RequestFactory; +import org.zalando.fahrschein.http.api.Response; + +import java.io.*; +import java.net.InetSocketAddress; +import java.net.SocketTimeoutException; +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import static java.nio.charset.StandardCharsets.UTF_8; +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.nullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.assertEquals; + +@RunWith(MockitoJUnitRunner.class) +public class SpringRequestFactoryTest { + + static HttpServer server; + static URI serverAddress; + + @BeforeClass + public static void startServer() throws IOException { + server = HttpServer.create(new InetSocketAddress("localhost", 0), 1); + serverAddress = URI.create("http://localhost:" + server.getAddress().getPort()); + ExecutorService executor = Executors.newSingleThreadExecutor(); + server.setExecutor(executor); + server.start(); + } + + @Captor + public ArgumentCaptor exchangeCaptor; + + @Test + public void testGzippedResponseBody() throws IOException { + // given + String expectedResponse = "{}"; + HttpHandler spy = Mockito.spy(new GzippedResponseContentHandler(expectedResponse)); + server.createContext("/gzipped", spy); + + // when + final RequestFactory f = defaultRequestFactory(); + f.disableContentCompression(); + Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); + Response executed = r.execute(); + String actualResponse = readStream(executed.getBody()); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertThat("accept-encoding header", capturedArgument.getRequestHeaders().get("accept-encoding"), equalTo(Arrays.asList("gzip"))); + assertThat("no content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), nullValue()); + assertEquals(URI.create("/gzipped"), capturedArgument.getRequestURI()); + assertEquals(expectedResponse, actualResponse); + } + + @Test(expected = SocketTimeoutException.class) + public void testTimeout() throws IOException { + // given + server.createContext("/timeout", exchange -> { + try { + Thread.sleep(10l); + exchange.sendResponseHeaders(201, 0); + } catch (InterruptedException e) { } + }); + + // when + OkHttpClient client = new OkHttpClient.Builder() + .readTimeout(1, TimeUnit.MILLISECONDS) + .build(); + OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); + SpringRequestFactory f = new SpringRequestFactory(clientHttpRequestFactory); + Request r = f.createRequest(serverAddress.resolve("/timeout"), "GET"); + r.execute(); + } + + @Test + public void testGzippedRequestBody() throws IOException { + // given + String requestBody = "{}"; + String responseBody = "{}"; + SimpleRequestResponseContentHandler spy = Mockito.spy(new SimpleRequestResponseContentHandler(responseBody)); + server.createContext("/gzipped-post", spy); + + // when + final RequestFactory f = defaultRequestFactory(); + Request r = f.createRequest(serverAddress.resolve("/gzipped-post"), "POST"); + r.getHeaders().setContentType(ContentType.APPLICATION_JSON); + try (final OutputStream body = r.getBody()) { + body.write(requestBody.getBytes()); + } + Response executed = r.execute(); + String actualResponse = readStream(executed.getBody()); + + // then + Mockito.verify(spy).handle(exchangeCaptor.capture()); + HttpExchange capturedArgument = exchangeCaptor.getValue(); + assertEquals("POST", capturedArgument.getRequestMethod()); + assertEquals(URI.create("/gzipped-post"), capturedArgument.getRequestURI()); + assertThat("content-encoding header", capturedArgument.getRequestHeaders().get("content-encoding"), equalTo(Arrays.asList("gzip"))); + assertEquals(requestBody, spy.getRequestBody()); + assertEquals(responseBody, actualResponse); + } + + @NotNull + private RequestFactory defaultRequestFactory() { + final OkHttpClient client = new OkHttpClient.Builder() + .build(); + final OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); + final SpringRequestFactory f = new SpringRequestFactory(clientHttpRequestFactory); + return f; + } + + static String readStream(InputStream stream) throws IOException { + String res = new BufferedReader( + new InputStreamReader(stream, UTF_8)) + .lines() + .collect(Collectors.joining("\n")); + stream.close(); + return res; + } + + private static class SimpleRequestResponseContentHandler implements HttpHandler { + + private String requestBody; + private final String responseBody; + + SimpleRequestResponseContentHandler(String responseBody) { + this.responseBody = responseBody; + } + + public String getRequestBody() { + return requestBody; + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + try { + if (exchange.getRequestHeaders().containsKey("Content-Encoding") && exchange.getRequestHeaders().get("Content-Encoding").contains("gzip")) { + requestBody = readStream(new GZIPInputStream(exchange.getRequestBody())); + } else { + requestBody = readStream(exchange.getRequestBody()); + } + } catch (Exception e) { + e.printStackTrace(); + } + + byte[] bytes = responseBody.getBytes(UTF_8); + exchange.sendResponseHeaders(200, bytes.length); + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(bytes); + responseBody.close(); + } + } + + private static class GzippedResponseContentHandler implements HttpHandler { + + private final byte[] rawResponse; + + GzippedResponseContentHandler(String response) throws IOException { + byte[] stringResponse = response.getBytes(UTF_8); + ByteArrayOutputStream byteStream = new ByteArrayOutputStream(); + GZIPOutputStream zipStream = new GZIPOutputStream(byteStream); + zipStream.write(stringResponse); + zipStream.close(); + this.rawResponse = byteStream.toByteArray(); + } + + @Override + public void handle(HttpExchange exchange) throws IOException { + exchange.getResponseHeaders().set("Content-Encoding", "gzip"); + exchange.sendResponseHeaders(200, rawResponse.length); + OutputStream responseBody = exchange.getResponseBody(); + responseBody.write(rawResponse); + responseBody.close(); + } + } +} diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java b/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java index 6749a1e0..ec5d6393 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java @@ -15,6 +15,11 @@ class AuthorizedRequestFactory implements RequestFactory { this.authorizationProvider = authorizationProvider; } + @Override + public void disableContentCompression() { + delegate.disableContentCompression(); + } + @Override public Request createRequest(URI uri, String method) throws IOException { final Request request = delegate.createRequest(uri, method); diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java b/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java index ddc18599..99501d2b 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java @@ -13,6 +13,11 @@ public ProblemHandlingRequestFactory(RequestFactory delegate) { this.delegate = delegate; } + @Override + public void disableContentCompression() { + delegate.disableContentCompression(); + } + @Override public Request createRequest(URI uri, String method) throws IOException { final Request request = delegate.createRequest(uri, method); diff --git a/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java b/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java index 8d26cc66..674829fc 100644 --- a/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java +++ b/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java @@ -159,6 +159,11 @@ public void verify() throws IOException { } } + @Override + public void disableContentCompression() { + requestFactory.disableContentCompression(); + } + @Override public Request createRequest(URI uri, String method) throws IOException { return requestFactory.createRequest(uri, method); From a9f7f7d7b78e88d41e162841d8d6dfc9beaf596e Mon Sep 17 00:00:00 2001 From: Oliver Date: Thu, 10 Mar 2022 18:11:41 +0100 Subject: [PATCH 03/14] Add note to README adds readme paragraph on compression --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 70d03e43..a2073c70 100644 --- a/README.md +++ b/README.md @@ -20,6 +20,7 @@ - No required base classes for events - Support for both high-level (subscription) and low-level APIs - Pluggable HTTP client implementations + - Gzip encoding for publishing and consuming events, enabled by default ## Installation @@ -245,7 +246,21 @@ final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) ``` -**Note:** The implementations from spring framework don't handle closing of streams as expected. They will try to consume remaining data, which will usually time out when nakadi does not receive a commit. +**Note:** The implementations from the Spring framework don't handle closing of streams as expected. They will try to consume remaining data, which will usually time out when nakadi does not receive a commit. + +## Content-Compression + +Fahrschein handles content compression transparently to the API consumer, and mostly independently of the actual HTTP client implementation. Since version `0.20.0` it is enabled by default, to both compress HTTP POST bodies for event-publishing, as well as requesting compression from the server when consuming events. + +### Consuming + +For event consumption the underlying HTTP client implementations send `Accept-Encoding` headers indicating the supported compression algorithm. At the time of writing, the default settings for all tested client implementations support `gzip` compression. + +If this is undesired, please add the following header to your request: `Accept-Encoding: identity`. + +### Publishing + +For event publishing, the `Request` body gets gzip-encoded by Fahrschein. To disable compression of the POST body, you need to call `disableContentCompression()` on your `RequestFactory`. ## Fahrschein compared to other Nakadi client libraries From 4f3c6544d8f4331dcd59612267339c7268510e2e Mon Sep 17 00:00:00 2001 From: Oliver Date: Fri, 18 Mar 2022 13:12:13 +0100 Subject: [PATCH 04/14] Fixes incompatibility with Nakadi when setting content-encoding For requests without body we mustn't set content-encoding headers. This fixes this bug, caught be end-to-end tests. --- fahrschein-e2e-test/pom.xml | 5 ++++ .../http/AbstractRequestFactoryTest.java | 4 +-- .../http/spring/SpringNakadiClientTest.java | 10 +++++++ .../src/test/resources/log4j2.xml | 26 +++++++++++++++++++ .../http/apache/HttpComponentsRequest.java | 15 +++++------ .../http/simple/SimpleBufferingRequest.java | 14 +++++----- .../http/spring/RequestAdapter.java | 19 +++++++++----- .../http/spring/SpringRequestFactory.java | 7 +---- 8 files changed, 70 insertions(+), 30 deletions(-) create mode 100644 fahrschein-e2e-test/src/test/resources/log4j2.xml diff --git a/fahrschein-e2e-test/pom.xml b/fahrschein-e2e-test/pom.xml index 264f5ee6..f0971552 100644 --- a/fahrschein-e2e-test/pom.xml +++ b/fahrschein-e2e-test/pom.xml @@ -40,6 +40,11 @@ okhttp 4.9.3 + + com.squareup.okhttp3 + logging-interceptor + 4.9.3 + org.springframework spring-web diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java index 7244edbf..2a04c713 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java @@ -87,10 +87,10 @@ public void testSubscribe() throws IOException, EventAlreadyProcessedException { return; })); testPublish(); - Mockito.verify(listener, timeout(10000).times(1)).accept(anyList()); + Mockito.verify(listener, timeout(10000).atLeastOnce()).accept(anyList()); } public Listener subscriptionListener() { - return Mockito.mock(Listener.class, withSettings().verboseLogging()); + return Mockito.mock(Listener.class); } } diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java index d06e4507..764332f7 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java @@ -1,5 +1,8 @@ package org.zalando.fahrschein.http.spring; +import okhttp3.logging.HttpLoggingInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.zalando.fahrschein.http.AbstractRequestFactoryTest; import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; @@ -10,9 +13,16 @@ public class SpringNakadiClientTest extends AbstractRequestFactoryTest { + private static final Logger logger = LoggerFactory.getLogger("okhttp3.wire"); + private static final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(logger::debug); + static { + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); + } + @Override protected RequestFactory getRequestFactory() { final OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) .readTimeout(60, TimeUnit.SECONDS) .connectTimeout(2, TimeUnit.SECONDS) .writeTimeout(10, TimeUnit.SECONDS) diff --git a/fahrschein-e2e-test/src/test/resources/log4j2.xml b/fahrschein-e2e-test/src/test/resources/log4j2.xml new file mode 100644 index 00000000..fdde9abd --- /dev/null +++ b/fahrschein-e2e-test/src/test/resources/log4j2.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java index c00a46af..8f06bbb7 100644 --- a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java +++ b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java @@ -4,7 +4,6 @@ import org.apache.http.HttpEntityEnclosingRequest; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.GzipCompressingEntity; import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.entity.ByteArrayEntity; import org.apache.http.protocol.HTTP; @@ -35,16 +34,18 @@ final class HttpComponentsRequest implements Request { private final HttpClient httpClient; private final HttpUriRequest httpRequest; - private final Boolean contentCompression; + private final boolean compressEntity; private final Headers headers; private ByteArrayOutputStream bufferedOutput; private boolean executed; - HttpComponentsRequest(HttpClient client, HttpUriRequest request, Boolean contentCompression) { + HttpComponentsRequest(HttpClient client, HttpUriRequest request, boolean enableContentCompression) { this.httpClient = client; this.httpRequest = request; - this.contentCompression = contentCompression; + this.compressEntity = enableContentCompression && + request.getFirstHeader("Content-Encoding") == null && + request instanceof HttpEntityEnclosingRequest; this.headers = new HeadersImpl(); } @@ -78,9 +79,6 @@ private Response executeInternal(Headers headers) throws IOException { HttpEntityEnclosingRequest entityEnclosingRequest = (HttpEntityEnclosingRequest) this.httpRequest; HttpEntity requestEntity = new ByteArrayEntity(bytes); entityEnclosingRequest.setEntity(requestEntity); - if(this.contentCompression && this.httpRequest.getFirstHeader("Content-Encoding") == null) { - this.httpRequest.setHeader("Content-Encoding", "gzip"); - } } final HttpResponse httpResponse = this.httpClient.execute(this.httpRequest); @@ -100,7 +98,8 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); - if (this.contentCompression) { + if (this.compressEntity) { + this.httpRequest.setHeader("Content-Encoding", "gzip"); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java index 3928c247..5618943d 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java @@ -30,14 +30,16 @@ final class SimpleBufferingRequest implements Request { private final HttpURLConnection connection; private final Headers headers; - private final Boolean contentCompression; + private final boolean compressEntity; private ByteArrayOutputStream bufferedOutput; private boolean executed; - SimpleBufferingRequest(HttpURLConnection connection, Boolean contentCompression) { + SimpleBufferingRequest(HttpURLConnection connection, boolean enableContentCompression) { this.connection = connection; this.headers = new HeadersImpl(); - this.contentCompression = contentCompression; + this.compressEntity = enableContentCompression && + this.connection.getRequestProperty("Content-Encoding") == null && + this.connection.getDoOutput(); } @Override @@ -79,9 +81,6 @@ private Response executeInternal() throws IOException { if (this.connection.getDoOutput()) { this.connection.setFixedLengthStreamingMode(size); - if(this.contentCompression && this.connection.getRequestProperty("Content-Encoding") == null) { - connection.setRequestProperty("Content-Encoding", "gzip"); - } } this.connection.connect(); @@ -110,7 +109,8 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); - if (this.contentCompression) { + if (this.compressEntity) { + connection.setRequestProperty("Content-Encoding", "gzip"); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java index dffb4446..e556a6a5 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java @@ -1,5 +1,6 @@ package org.zalando.fahrschein.http.spring; +import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; import org.zalando.fahrschein.http.api.Headers; import org.zalando.fahrschein.http.api.Request; @@ -8,19 +9,22 @@ import java.io.IOException; import java.io.OutputStream; import java.net.URI; +import java.util.Arrays; +import java.util.List; import java.util.zip.GZIPOutputStream; class RequestAdapter implements Request { private final ClientHttpRequest clientHttpRequest; - private final boolean contentCompression; + private final boolean compressEntity; + + private static final List writeMethods = Arrays.asList(HttpMethod.POST, HttpMethod.PATCH, HttpMethod.PUT); RequestAdapter(ClientHttpRequest clientHttpRequest, Boolean contentCompression) { this.clientHttpRequest = clientHttpRequest; - this.contentCompression = contentCompression; - - if (contentCompression) { - clientHttpRequest.getHeaders().set("Content-Encoding", "gzip"); - } + // only compress request if + this.compressEntity = contentCompression && + !clientHttpRequest.getHeaders().containsKey("Content-Encoding") && + writeMethods.contains(clientHttpRequest.getMethod()); } @Override @@ -40,7 +44,8 @@ public Headers getHeaders() { @Override public OutputStream getBody() throws IOException { - if (this.contentCompression) { + if (compressEntity) { + clientHttpRequest.getHeaders().set("Content-Encoding", "gzip"); return new GZIPOutputStream(clientHttpRequest.getBody()); } return clientHttpRequest.getBody(); diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java index 625324d2..9f0a192d 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java @@ -10,15 +10,10 @@ public class SpringRequestFactory implements RequestFactory { private final ClientHttpRequestFactory clientRequestFactory; - private Boolean contentCompression; + private boolean contentCompression = true; public SpringRequestFactory(ClientHttpRequestFactory clientRequestFactory) { - this(clientRequestFactory, true); - } - - public SpringRequestFactory(ClientHttpRequestFactory clientRequestFactory, Boolean contentCompression) { this.clientRequestFactory = clientRequestFactory; - this.contentCompression = contentCompression; } @Override From 9b637624a31fd6c53fa6c751fa2af34bd202cd33 Mon Sep 17 00:00:00 2001 From: Oliver Date: Mon, 21 Mar 2022 11:54:34 +0100 Subject: [PATCH 05/14] Address PR review comments * Removed x-flow-id from log4j2.xml * Explicit imports * test-scope for fahrschein-e2e-test dependencies --- fahrschein-e2e-test/pom.xml | 14 ++++++++++++-- fahrschein-e2e-test/src/test/resources/log4j2.xml | 2 +- fahrschein-example/src/main/resources/log4j2.xml | 2 +- .../apache/HttpComponentsRequestFactoryTest.java | 7 ++++++- fahrschein-jdbc/src/test/resources/log4j2.xml | 2 +- fahrschein/src/test/resources/log4j2.xml | 2 +- 6 files changed, 22 insertions(+), 7 deletions(-) diff --git a/fahrschein-e2e-test/pom.xml b/fahrschein-e2e-test/pom.xml index f0971552..fc81563a 100644 --- a/fahrschein-e2e-test/pom.xml +++ b/fahrschein-e2e-test/pom.xml @@ -19,36 +19,43 @@ org.zalando fahrschein ${project.version} + test org.zalando fahrschein-http-api ${project.version} + test org.zalando fahrschein-http-apache ${project.version} + test org.zalando fahrschein-http-spring ${project.version} + test com.squareup.okhttp3 okhttp 4.9.3 + test com.squareup.okhttp3 logging-interceptor 4.9.3 + test org.springframework spring-web ${version.spring} + test org.springframework @@ -68,29 +75,32 @@ org.slf4j slf4j-api ${version.slf4j} + test org.apache.logging.log4j log4j-api ${version.log4j} + test org.apache.logging.log4j log4j-core ${version.log4j} + test org.apache.logging.log4j log4j-slf4j-impl ${version.log4j} + test org.slf4j jcl-over-slf4j ${version.slf4j} + test - - junit junit diff --git a/fahrschein-e2e-test/src/test/resources/log4j2.xml b/fahrschein-e2e-test/src/test/resources/log4j2.xml index fdde9abd..8afed281 100644 --- a/fahrschein-e2e-test/src/test/resources/log4j2.xml +++ b/fahrschein-e2e-test/src/test/resources/log4j2.xml @@ -4,7 +4,7 @@ + pattern="%date {%level} [%thread] [%logger] %message%n"/> diff --git a/fahrschein-example/src/main/resources/log4j2.xml b/fahrschein-example/src/main/resources/log4j2.xml index 774b2bcf..e6eb46a4 100644 --- a/fahrschein-example/src/main/resources/log4j2.xml +++ b/fahrschein-example/src/main/resources/log4j2.xml @@ -4,7 +4,7 @@ + pattern="%date {%level} [%thread] [%logger] %message%n"/> diff --git a/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java b/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java index c32456af..68311224 100644 --- a/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java +++ b/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java @@ -18,7 +18,12 @@ import org.zalando.fahrschein.http.api.RequestFactory; import org.zalando.fahrschein.http.api.Response; -import java.io.*; +import java.io.BufferedReader; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; import java.net.InetSocketAddress; import java.net.SocketTimeoutException; import java.net.URI; diff --git a/fahrschein-jdbc/src/test/resources/log4j2.xml b/fahrschein-jdbc/src/test/resources/log4j2.xml index 3fe57178..4822b7a1 100644 --- a/fahrschein-jdbc/src/test/resources/log4j2.xml +++ b/fahrschein-jdbc/src/test/resources/log4j2.xml @@ -4,7 +4,7 @@ + pattern="%date {%level} [%thread] [%logger] %message%n"/> diff --git a/fahrschein/src/test/resources/log4j2.xml b/fahrschein/src/test/resources/log4j2.xml index 5794d77c..81fbc33e 100644 --- a/fahrschein/src/test/resources/log4j2.xml +++ b/fahrschein/src/test/resources/log4j2.xml @@ -4,7 +4,7 @@ + pattern="%date {%level} [%thread] [%logger] %message%n"/> From ca458452975e12868a36871f0f0b3164a79ab5ca Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 22 Mar 2022 12:22:04 +0100 Subject: [PATCH 06/14] Make contentCompression a mandatory choice for teams --- .../http/AbstractRequestFactoryTest.java | 3 +-- .../http/apache/ApacheNakadiClientTest.java | 3 ++- .../http/simple/SimpleNakadiClientTest.java | 3 ++- .../http/spring/SpringNakadiClientTest.java | 3 ++- .../org/zalando/fahrschein/example/Main.java | 26 +++++++++---------- .../http/apache/HttpComponentsRequest.java | 16 +++++++----- .../apache/HttpComponentsRequestFactory.java | 14 +++++----- .../HttpComponentsRequestFactoryTest.java | 13 +++++----- .../fahrschein/http/api/ContentEncoding.java | 15 +++++++++++ .../fahrschein/http/api/RequestFactory.java | 5 ---- .../http/simple/SimpleBufferingRequest.java | 15 ++++++----- .../http/simple/SimpleRequestFactory.java | 21 ++++++++------- .../http/simple/SimpleRequestFactoryTest.java | 18 ++++++------- .../http/spring/RequestAdapter.java | 16 +++++------- .../http/spring/SpringRequestFactory.java | 13 ++++------ .../http/spring/SpringRequestFactoryTest.java | 12 ++++----- .../fahrschein/AuthorizedRequestFactory.java | 5 ---- .../org/zalando/fahrschein/NakadiClient.java | 4 +-- .../fahrschein/NakadiClientBuilder.java | 19 +++----------- .../ProblemHandlingRequestFactory.java | 5 ---- .../org/zalando/fahrschein/MockServer.java | 5 ---- .../zalando/fahrschein/NakadiClientTest.java | 4 +-- 22 files changed, 109 insertions(+), 129 deletions(-) create mode 100644 fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java index 2a04c713..bcadf072 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java @@ -42,9 +42,8 @@ public abstract class AbstractRequestFactoryTest extends NakadiTestWithDockerCom @Before public void setUpNakadiClient() { nakadiClient = NakadiClient - .builder(getNakadiUrl()) + .builder(getNakadiUrl(), getRequestFactory()) .withObjectMapper(objectMapper) - .withRequestFactory(getRequestFactory()) .build(); } diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java index 6fed9067..0a18cadd 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java @@ -5,6 +5,7 @@ import org.apache.http.config.ConnectionConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.RequestFactory; import java.util.concurrent.TimeUnit; @@ -34,7 +35,7 @@ protected RequestFactory getRequestFactory() { .setMaxConnPerRoute(2) .build(); - return new HttpComponentsRequestFactory(httpClient); + return new HttpComponentsRequestFactory(httpClient, ContentEncoding.GZIP); } } diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/SimpleNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/SimpleNakadiClientTest.java index 112bd55a..5b3e693b 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/SimpleNakadiClientTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/SimpleNakadiClientTest.java @@ -1,11 +1,12 @@ package org.zalando.fahrschein.http.simple; import org.zalando.fahrschein.http.AbstractRequestFactoryTest; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.RequestFactory; public class SimpleNakadiClientTest extends AbstractRequestFactoryTest { @Override protected RequestFactory getRequestFactory() { - return new SimpleRequestFactory(); + return new SimpleRequestFactory(ContentEncoding.GZIP); } } diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java index 764332f7..fa49a6ad 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java @@ -7,6 +7,7 @@ import okhttp3.ConnectionPool; import okhttp3.OkHttpClient; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.RequestFactory; import java.util.concurrent.TimeUnit; @@ -30,7 +31,7 @@ protected RequestFactory getRequestFactory() { .build(); final OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); - return new SpringRequestFactory(clientHttpRequestFactory); + return new SpringRequestFactory(clientHttpRequestFactory, ContentEncoding.GZIP); } } diff --git a/fahrschein-example/src/main/java/org/zalando/fahrschein/example/Main.java b/fahrschein-example/src/main/java/org/zalando/fahrschein/example/Main.java index 036c253a..abc70939 100644 --- a/fahrschein-example/src/main/java/org/zalando/fahrschein/example/Main.java +++ b/fahrschein-example/src/main/java/org/zalando/fahrschein/example/Main.java @@ -31,7 +31,9 @@ import org.zalando.fahrschein.example.domain.SalesOrder; import org.zalando.fahrschein.example.domain.SalesOrderPlaced; import org.zalando.fahrschein.http.apache.HttpComponentsRequestFactory; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.RequestFactory; +import org.zalando.fahrschein.http.simple.SimpleRequestFactory; import org.zalando.fahrschein.http.spring.SpringRequestFactory; import org.zalando.fahrschein.inmemory.InMemoryCursorManager; import org.zalando.fahrschein.jdbc.JdbcCursorManager; @@ -121,7 +123,7 @@ private static void subscriptionMultipleEvents(ObjectMapper objectMapper) throws } }; - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -149,7 +151,7 @@ private static void subscriptionListenWithPositionCursors(ObjectMapper objectMap new Cursor("6", "000000000000109100", "sales-order-service.order-placed"), new Cursor("7", "000000000000109146", "sales-order-service.order-placed")); - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -165,7 +167,7 @@ private static void subscriptionListenWithPositionCursors(ObjectMapper objectMap private static void subscriptionListen(ObjectMapper objectMapper, Listener listener) throws IOException { - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -201,10 +203,9 @@ private static void subscriptionListenHttpComponents(ObjectMapper objectMapper, .setMaxConnPerRoute(2) .build(); - final RequestFactory requestFactory = new HttpComponentsRequestFactory(httpClient); + final RequestFactory requestFactory = new HttpComponentsRequestFactory(httpClient, ContentEncoding.IDENTITY); - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) - .withRequestFactory(requestFactory) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, requestFactory) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -231,10 +232,9 @@ private static void subscriptionListenSpringAdapter(ObjectMapper objectMapper, L .build(); final OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); - final SpringRequestFactory requestFactory = new SpringRequestFactory(clientHttpRequestFactory); + final SpringRequestFactory requestFactory = new SpringRequestFactory(clientHttpRequestFactory, ContentEncoding.IDENTITY); - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) - .withRequestFactory(requestFactory) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, requestFactory) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -252,7 +252,7 @@ private static void subscriptionListenSpringAdapter(ObjectMapper objectMapper, L private static void simpleListen(ObjectMapper objectMapper, Listener listener) throws IOException { final InMemoryCursorManager cursorManager = new InMemoryCursorManager(); - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .withCursorManager(cursorManager) .build(); @@ -276,7 +276,7 @@ private static void persistentListen(ObjectMapper objectMapper, Listener listener) throws IOException { - final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) + final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -328,7 +328,7 @@ private static void multiInstanceListen(ObjectMapper objectMapper, Listener writeMethods = Arrays.asList("POST", "PATCH", "PUT"); + + HttpComponentsRequest(HttpClient client, HttpUriRequest request, ContentEncoding contentEncoding) { this.httpClient = client; this.httpRequest = request; - this.compressEntity = enableContentCompression && - request.getFirstHeader("Content-Encoding") == null && - request instanceof HttpEntityEnclosingRequest; + this.contentEncoding = contentEncoding; this.headers = new HeadersImpl(); } @@ -98,8 +100,8 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); - if (this.compressEntity) { - this.httpRequest.setHeader("Content-Encoding", "gzip"); + if (writeMethods.contains(getMethod()) && ContentEncoding.GZIP.equals(this.contentEncoding)) { + this.httpRequest.setHeader("Content-Encoding", this.contentEncoding.getEncoding()); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java index 25efb36c..76a260c5 100644 --- a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java +++ b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactory.java @@ -10,6 +10,7 @@ import org.apache.http.client.methods.HttpPut; import org.apache.http.client.methods.HttpTrace; import org.apache.http.client.methods.HttpUriRequest; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.RequestFactory; @@ -35,30 +36,27 @@ public class HttpComponentsRequestFactory implements RequestFactory { private final HttpClient httpClient; - private boolean contentCompression = true; + private final ContentEncoding contentEncoding; /** * Create a new instance of the {@code HttpComponentsRequestFactory} * with the given {@link HttpClient} instance. * @param httpClient the HttpClient instance to use for this request factory + * @param contentEncoding content encoding for request payloads. */ - public HttpComponentsRequestFactory(HttpClient httpClient) { + public HttpComponentsRequestFactory(HttpClient httpClient, ContentEncoding contentEncoding) { + this.contentEncoding = contentEncoding; if (httpClient == null) { throw new IllegalArgumentException("HttpClient must not be null"); } this.httpClient = httpClient; } - @Override - public void disableContentCompression() { - this.contentCompression = false; - } - @Override public Request createRequest(URI uri, String httpMethod) throws IOException { final HttpUriRequest httpRequest = createHttpUriRequest(httpMethod, uri); - return new HttpComponentsRequest(httpClient, httpRequest, contentCompression); + return new HttpComponentsRequest(httpClient, httpRequest, contentEncoding); } /** diff --git a/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java b/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java index 68311224..e34c0be0 100644 --- a/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java +++ b/fahrschein-http-apache/src/test/java/org/zalando/fahrschein/http/apache/HttpComponentsRequestFactoryTest.java @@ -13,6 +13,7 @@ import org.mockito.Captor; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.ContentType; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.RequestFactory; @@ -66,8 +67,7 @@ public void testGzippedResponseBody() throws IOException { server.createContext("/gzipped", spy); // when - final RequestFactory f = defaultRequestFactory(); - f.disableContentCompression(); + final RequestFactory f = defaultRequestFactory(ContentEncoding.IDENTITY); Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); Response executed = r.execute(); String actualResponse = readStream(executed.getBody()); @@ -94,7 +94,7 @@ public void testTimeout() throws IOException { // when RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(1).build(); final CloseableHttpClient httpClient = HttpClients.custom().setDefaultRequestConfig(requestConfig).build(); - RequestFactory f = new HttpComponentsRequestFactory(httpClient); + RequestFactory f = new HttpComponentsRequestFactory(httpClient, ContentEncoding.GZIP); Request r = f.createRequest(serverAddress.resolve("/timeout"), "GET"); r.execute(); } @@ -108,8 +108,7 @@ public void testGzippedRequestBody() throws IOException { server.createContext("/gzipped-post", spy); // when - CloseableHttpClient c = HttpClients.createDefault(); - HttpComponentsRequestFactory f = new HttpComponentsRequestFactory(c); + RequestFactory f = defaultRequestFactory(ContentEncoding.GZIP); Request r = f.createRequest(serverAddress.resolve("/gzipped-post"), "POST"); r.getHeaders().setContentType(ContentType.APPLICATION_JSON); @@ -129,8 +128,8 @@ public void testGzippedRequestBody() throws IOException { assertEquals(responseBody, actualResponse); } - private RequestFactory defaultRequestFactory() { - return new HttpComponentsRequestFactory(HttpClients.createDefault()); + private RequestFactory defaultRequestFactory(ContentEncoding contentEncoding) { + return new HttpComponentsRequestFactory(HttpClients.createDefault(), contentEncoding); } static String readStream(InputStream stream) throws IOException { diff --git a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java new file mode 100644 index 00000000..5fe29645 --- /dev/null +++ b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java @@ -0,0 +1,15 @@ +package org.zalando.fahrschein.http.api; + +public enum ContentEncoding { + IDENTITY("identity"), GZIP("gzip"); + + private final String encoding; + + ContentEncoding(String encoding) { + this.encoding = encoding; + } + + public String getEncoding() { + return encoding; + } +} diff --git a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java index 9cc12759..b5496b4d 100644 --- a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java +++ b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/RequestFactory.java @@ -5,11 +5,6 @@ public interface RequestFactory { - /** - * By default, POST-content is gzip-compressed. This disables content compression. - */ - void disableContentCompression(); - /** * Creates a new request using the underlying RequestFactory implementation. * @param uri request target URI diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java index 5618943d..de5e2f91 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java @@ -1,5 +1,6 @@ package org.zalando.fahrschein.http.simple; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.Headers; import org.zalando.fahrschein.http.api.HeadersImpl; import org.zalando.fahrschein.http.api.Request; @@ -14,6 +15,8 @@ import java.util.List; import java.util.zip.GZIPOutputStream; +import static org.zalando.fahrschein.http.api.ContentEncoding.GZIP; + /** * {@link Request} implementation that uses standard JDK facilities to * execute buffered requests. Created via the {@link SimpleRequestFactory}. @@ -30,16 +33,14 @@ final class SimpleBufferingRequest implements Request { private final HttpURLConnection connection; private final Headers headers; - private final boolean compressEntity; + private final ContentEncoding contentEncoding; private ByteArrayOutputStream bufferedOutput; private boolean executed; - SimpleBufferingRequest(HttpURLConnection connection, boolean enableContentCompression) { + SimpleBufferingRequest(HttpURLConnection connection, ContentEncoding contentEncoding) { this.connection = connection; this.headers = new HeadersImpl(); - this.compressEntity = enableContentCompression && - this.connection.getRequestProperty("Content-Encoding") == null && - this.connection.getDoOutput(); + this.contentEncoding = contentEncoding; } @Override @@ -109,8 +110,8 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); - if (this.compressEntity) { - connection.setRequestProperty("Content-Encoding", "gzip"); + if (this.connection.getDoOutput() && GZIP.equals(this.contentEncoding)) { + this.connection.setRequestProperty("Content-Encoding", this.contentEncoding.getEncoding()); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java index d0ba0f28..d80dcc48 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleRequestFactory.java @@ -1,5 +1,6 @@ package org.zalando.fahrschein.http.simple; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.RequestFactory; @@ -22,9 +23,16 @@ */ public class SimpleRequestFactory implements RequestFactory { - private int connectTimeout = -1; - private int readTimeout = -1; - private boolean contentCompression = true; + private static final int DEFAULT_CONNECT_TIMEOUT = 500; + private static final int DEFAULT_READ_TIMEOUT = 60 * 1000; + + private int connectTimeout = DEFAULT_CONNECT_TIMEOUT; + private int readTimeout = DEFAULT_READ_TIMEOUT; + private final ContentEncoding contentEncoding; + + public SimpleRequestFactory(ContentEncoding contentEncoding) { + this.contentEncoding = contentEncoding; + } /** * Set the underlying URLConnection's connect timeout (in milliseconds). @@ -48,17 +56,12 @@ public void setReadTimeout(int readTimeout) { this.readTimeout = readTimeout; } - @Override - public void disableContentCompression() { - this.contentCompression = false; - } - @Override public Request createRequest(URI uri, String method) throws IOException { HttpURLConnection connection = openConnection(uri.toURL()); prepareConnection(connection, method); - return new SimpleBufferingRequest(connection, contentCompression); + return new SimpleBufferingRequest(connection, contentEncoding); } /** diff --git a/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java b/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java index 61b675ee..e4701ea4 100644 --- a/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java +++ b/fahrschein-http-simple/src/test/java/org/zalando/fahrschein/http/simple/SimpleRequestFactoryTest.java @@ -10,6 +10,7 @@ import org.mockito.Captor; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.ContentType; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.RequestFactory; @@ -56,8 +57,7 @@ public void testGetRequest() throws IOException { server.createContext("/get", spy); // when - RequestFactory f = getRequestFactory(); - f.disableContentCompression(); + RequestFactory f = defaultRequestFactory(ContentEncoding.GZIP); Request r = f.createRequest(serverAddress.resolve("/get"), "GET"); Response executed = r.execute(); String actualResponse = readStream(executed.getBody()); @@ -81,8 +81,7 @@ public void testPostRequest() throws IOException { server.createContext("/post", spy); // when - SimpleRequestFactory f = new SimpleRequestFactory(); - f.disableContentCompression(); + SimpleRequestFactory f = new SimpleRequestFactory(ContentEncoding.IDENTITY); Request r = f.createRequest(serverAddress.resolve("/post"), "POST"); r.getHeaders().setContentType(ContentType.APPLICATION_JSON); try (final OutputStream body = r.getBody()) { @@ -109,8 +108,7 @@ public void testGzippedResponseBody() throws IOException { server.createContext("/gzipped", spy); // when - RequestFactory f = getRequestFactory(); - f.disableContentCompression(); + RequestFactory f = defaultRequestFactory(ContentEncoding.IDENTITY); Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); Response executed = r.execute(); String actualResponse = readStream(executed.getBody()); @@ -135,7 +133,7 @@ public void testTimeout() throws IOException { }); // when - SimpleRequestFactory f = new SimpleRequestFactory(); + SimpleRequestFactory f = new SimpleRequestFactory(ContentEncoding.IDENTITY); f.setReadTimeout(1); Request r = f.createRequest(serverAddress.resolve("/timeout"), "GET"); r.execute(); @@ -150,7 +148,7 @@ public void testGzippedRequestBody() throws IOException { server.createContext("/gzipped-post", spy); // when - RequestFactory f = getRequestFactory(); + RequestFactory f = defaultRequestFactory(ContentEncoding.GZIP); Request r = f.createRequest(serverAddress.resolve("/gzipped-post"), "POST"); r.getHeaders().setContentType(ContentType.APPLICATION_JSON); try (final OutputStream body = r.getBody()) { @@ -169,8 +167,8 @@ public void testGzippedRequestBody() throws IOException { assertEquals(responseBody, actualResponse); } - private RequestFactory getRequestFactory() { - return new SimpleRequestFactory(); + private RequestFactory defaultRequestFactory(ContentEncoding contentEncoding) { + return new SimpleRequestFactory(contentEncoding); } static String readStream(InputStream stream) throws IOException { diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java index e556a6a5..46498791 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequest; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.Headers; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.Response; @@ -15,16 +16,13 @@ class RequestAdapter implements Request { private final ClientHttpRequest clientHttpRequest; - private final boolean compressEntity; + private final ContentEncoding contentEncoding; - private static final List writeMethods = Arrays.asList(HttpMethod.POST, HttpMethod.PATCH, HttpMethod.PUT); + private static final List writeMethods = Arrays.asList("POST", "PATCH", "PUT"); - RequestAdapter(ClientHttpRequest clientHttpRequest, Boolean contentCompression) { + RequestAdapter(ClientHttpRequest clientHttpRequest, ContentEncoding contentEncoding) { this.clientHttpRequest = clientHttpRequest; - // only compress request if - this.compressEntity = contentCompression && - !clientHttpRequest.getHeaders().containsKey("Content-Encoding") && - writeMethods.contains(clientHttpRequest.getMethod()); + this.contentEncoding = contentEncoding; } @Override @@ -44,8 +42,8 @@ public Headers getHeaders() { @Override public OutputStream getBody() throws IOException { - if (compressEntity) { - clientHttpRequest.getHeaders().set("Content-Encoding", "gzip"); + if (writeMethods.contains(getMethod()) && ContentEncoding.GZIP.equals(contentEncoding)) { + clientHttpRequest.getHeaders().set("Content-Encoding", contentEncoding.getEncoding()); return new GZIPOutputStream(clientHttpRequest.getBody()); } return clientHttpRequest.getBody(); diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java index 9f0a192d..ab4ab1e7 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/SpringRequestFactory.java @@ -2,6 +2,7 @@ import org.springframework.http.HttpMethod; import org.springframework.http.client.ClientHttpRequestFactory; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.RequestFactory; @@ -10,19 +11,15 @@ public class SpringRequestFactory implements RequestFactory { private final ClientHttpRequestFactory clientRequestFactory; - private boolean contentCompression = true; + private final ContentEncoding contentEncoding; - public SpringRequestFactory(ClientHttpRequestFactory clientRequestFactory) { + public SpringRequestFactory(ClientHttpRequestFactory clientRequestFactory, ContentEncoding contentEncoding) { this.clientRequestFactory = clientRequestFactory; - } - - @Override - public void disableContentCompression() { - this.contentCompression = false; + this.contentEncoding = contentEncoding; } @Override public Request createRequest(URI uri, String method) throws IOException { - return new RequestAdapter(clientRequestFactory.createRequest(uri, HttpMethod.valueOf(method)), contentCompression); + return new RequestAdapter(clientRequestFactory.createRequest(uri, HttpMethod.valueOf(method)), contentEncoding); } } diff --git a/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java b/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java index 043160f0..65da8646 100644 --- a/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java +++ b/fahrschein-http-spring/src/test/java/org/zalando/fahrschein/http/spring/SpringRequestFactoryTest.java @@ -13,6 +13,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.ContentType; import org.zalando.fahrschein.http.api.Request; import org.zalando.fahrschein.http.api.RequestFactory; @@ -62,8 +63,7 @@ public void testGzippedResponseBody() throws IOException { server.createContext("/gzipped", spy); // when - final RequestFactory f = defaultRequestFactory(); - f.disableContentCompression(); + final RequestFactory f = defaultRequestFactory(ContentEncoding.IDENTITY); Request r = f.createRequest(serverAddress.resolve("/gzipped"), "GET"); Response executed = r.execute(); String actualResponse = readStream(executed.getBody()); @@ -92,7 +92,7 @@ public void testTimeout() throws IOException { .readTimeout(1, TimeUnit.MILLISECONDS) .build(); OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); - SpringRequestFactory f = new SpringRequestFactory(clientHttpRequestFactory); + SpringRequestFactory f = new SpringRequestFactory(clientHttpRequestFactory, ContentEncoding.IDENTITY); Request r = f.createRequest(serverAddress.resolve("/timeout"), "GET"); r.execute(); } @@ -106,7 +106,7 @@ public void testGzippedRequestBody() throws IOException { server.createContext("/gzipped-post", spy); // when - final RequestFactory f = defaultRequestFactory(); + final RequestFactory f = defaultRequestFactory(ContentEncoding.GZIP); Request r = f.createRequest(serverAddress.resolve("/gzipped-post"), "POST"); r.getHeaders().setContentType(ContentType.APPLICATION_JSON); try (final OutputStream body = r.getBody()) { @@ -126,11 +126,11 @@ public void testGzippedRequestBody() throws IOException { } @NotNull - private RequestFactory defaultRequestFactory() { + private RequestFactory defaultRequestFactory(ContentEncoding contentEncoding) { final OkHttpClient client = new OkHttpClient.Builder() .build(); final OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); - final SpringRequestFactory f = new SpringRequestFactory(clientHttpRequestFactory); + final SpringRequestFactory f = new SpringRequestFactory(clientHttpRequestFactory, contentEncoding); return f; } diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java b/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java index ec5d6393..6749a1e0 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/AuthorizedRequestFactory.java @@ -15,11 +15,6 @@ class AuthorizedRequestFactory implements RequestFactory { this.authorizationProvider = authorizationProvider; } - @Override - public void disableContentCompression() { - delegate.disableContentCompression(); - } - @Override public Request createRequest(URI uri, String method) throws IOException { final Request request = delegate.createRequest(uri, method); diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClient.java b/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClient.java index fc01a280..3f21f0d8 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClient.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClient.java @@ -38,8 +38,8 @@ public class NakadiClient { private final ObjectMapper objectMapper; private final CursorManager cursorManager; - public static NakadiClientBuilder builder(URI baseUri) { - return new NakadiClientBuilder(baseUri); + public static NakadiClientBuilder builder(URI baseUri, RequestFactory clientHttpRequestFactory) { + return new NakadiClientBuilder(baseUri, clientHttpRequestFactory); } NakadiClient(URI baseUri, RequestFactory clientHttpRequestFactory, ObjectMapper objectMapper, CursorManager cursorManager) { diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClientBuilder.java b/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClientBuilder.java index 9db1928e..c529249d 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClientBuilder.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/NakadiClientBuilder.java @@ -9,8 +9,6 @@ import static org.zalando.fahrschein.Preconditions.checkNotNull; public final class NakadiClientBuilder { - public static final int DEFAULT_CONNECT_TIMEOUT = 500; - public static final int DEFAULT_READ_TIMEOUT = 60 * 1000; private final URI baseUri; @Nullable @@ -22,8 +20,8 @@ public final class NakadiClientBuilder { @Nullable private final CursorManager cursorManager; - NakadiClientBuilder(final URI baseUri) { - this(baseUri, DefaultObjectMapper.INSTANCE, null, null, null); + NakadiClientBuilder(final URI baseUri, RequestFactory requestFactory) { + this(baseUri, DefaultObjectMapper.INSTANCE, null, requestFactory, null); } private NakadiClientBuilder(URI baseUri, @Nullable ObjectMapper objectMapper, @Nullable AuthorizationProvider authorizationProvider, @Nullable RequestFactory clientHttpRequestFactory, @Nullable CursorManager cursorManager) { @@ -46,21 +44,10 @@ public NakadiClientBuilder withAuthorizationProvider(AuthorizationProvider autho return new NakadiClientBuilder(baseUri, objectMapper, authorizationProvider, clientHttpRequestFactory, cursorManager); } - public NakadiClientBuilder withRequestFactory(RequestFactory clientHttpRequestFactory) { - return new NakadiClientBuilder(baseUri, objectMapper, authorizationProvider, clientHttpRequestFactory, cursorManager); - } - public NakadiClientBuilder withCursorManager(CursorManager cursorManager) { return new NakadiClientBuilder(baseUri, objectMapper, authorizationProvider, clientHttpRequestFactory, cursorManager); } - private RequestFactory defaultClientHttpRequestFactory() { - final SimpleRequestFactory clientHttpRequestFactory = new SimpleRequestFactory(); - clientHttpRequestFactory.setConnectTimeout(DEFAULT_CONNECT_TIMEOUT); - clientHttpRequestFactory.setReadTimeout(DEFAULT_READ_TIMEOUT); - return clientHttpRequestFactory; - } - static RequestFactory wrapClientHttpRequestFactory(RequestFactory delegate, @Nullable AuthorizationProvider authorizationProvider) { RequestFactory requestFactory = new ProblemHandlingRequestFactory(delegate); if (authorizationProvider != null) { @@ -71,7 +58,7 @@ static RequestFactory wrapClientHttpRequestFactory(RequestFactory delegate, @Nul } public NakadiClient build() { - final RequestFactory clientHttpRequestFactory = wrapClientHttpRequestFactory(this.clientHttpRequestFactory != null ? this.clientHttpRequestFactory : defaultClientHttpRequestFactory(), authorizationProvider); + final RequestFactory clientHttpRequestFactory = wrapClientHttpRequestFactory(this.clientHttpRequestFactory, authorizationProvider); final CursorManager cursorManager = this.cursorManager != null ? this.cursorManager : new ManagedCursorManager(baseUri, clientHttpRequestFactory, true); final ObjectMapper objectMapper = this.objectMapper != null ? this.objectMapper : DefaultObjectMapper.INSTANCE; diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java b/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java index 99501d2b..ddc18599 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/ProblemHandlingRequestFactory.java @@ -13,11 +13,6 @@ public ProblemHandlingRequestFactory(RequestFactory delegate) { this.delegate = delegate; } - @Override - public void disableContentCompression() { - delegate.disableContentCompression(); - } - @Override public Request createRequest(URI uri, String method) throws IOException { final Request request = delegate.createRequest(uri, method); diff --git a/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java b/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java index 674829fc..8d26cc66 100644 --- a/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java +++ b/fahrschein/src/test/java/org/zalando/fahrschein/MockServer.java @@ -159,11 +159,6 @@ public void verify() throws IOException { } } - @Override - public void disableContentCompression() { - requestFactory.disableContentCompression(); - } - @Override public Request createRequest(URI uri, String method) throws IOException { return requestFactory.createRequest(uri, method); diff --git a/fahrschein/src/test/java/org/zalando/fahrschein/NakadiClientTest.java b/fahrschein/src/test/java/org/zalando/fahrschein/NakadiClientTest.java index 0bf40ad0..09468861 100644 --- a/fahrschein/src/test/java/org/zalando/fahrschein/NakadiClientTest.java +++ b/fahrschein/src/test/java/org/zalando/fahrschein/NakadiClientTest.java @@ -8,6 +8,7 @@ import org.zalando.fahrschein.domain.Subscription; import org.zalando.fahrschein.domain.SubscriptionRequest; import org.zalando.fahrschein.http.api.ContentType; +import org.zalando.fahrschein.http.simple.SimpleRequestFactory; import java.io.IOException; import java.net.URI; @@ -50,8 +51,7 @@ public void setup() { final CursorManager cursorManager = mock(CursorManager.class); - final NakadiClient nakadiClient = NakadiClient.builder(URI.create("http://example.com/")) - .withRequestFactory(clientHttpRequestFactory) + final NakadiClient nakadiClient = NakadiClient.builder(URI.create("http://example.com/"), clientHttpRequestFactory) .withCursorManager(cursorManager) .build(); From 30f17397781539b200a148a6ec346d7854c8641a Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 22 Mar 2022 13:12:34 +0100 Subject: [PATCH 07/14] Updated README wrt compression --- README.md | 29 +++++++++---------- .../http/simple/SimpleBufferingRequest.java | 4 +-- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index a2073c70..5c328496 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ - No required base classes for events - Support for both high-level (subscription) and low-level APIs - Pluggable HTTP client implementations - - Gzip encoding for publishing and consuming events, enabled by default + - Gzip encoding support for publishing and consuming events ## Installation @@ -46,8 +46,9 @@ final Listener listener = events -> { } }; -// Configure client, defaults to using the high level api with ManagedCursorManger and SimpleRequestFactory -final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) +// Configure client, defaults to using the high level api with ManagedCursorManger, +// using the SimpleRequestFactory without compression +final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -99,7 +100,7 @@ final DataSource dataSource = new HikariDataSource(hikariConfig); final CursorManager cursorManager = new JdbcCursorManager(dataSource, "fahrschein-demo"); -final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) +final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, new SimpleRequestFactory(ContentEncoding.IDENTITY)) .withAccessTokenProvider(new ZignAccessTokenProvider()) .withCursorManager(cursorManager) .build(); @@ -214,10 +215,9 @@ final CloseableHttpClient httpClient = HttpClients.custom() .setMaxConnPerRoute(2) .build(); -final RequestFactory requestFactory = new HttpComponentsRequestFactory(httpClient); +final RequestFactory requestFactory = new HttpComponentsRequestFactory(httpClient, ContentEncoding.GZIP); -final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) - .withRequestFactory(requestFactory) +final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, requestFactory) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); ``` @@ -237,10 +237,9 @@ Example using OkHttp 3.x: ```java final ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(); -final RequestFactory requestFactory = new SpringRequestFactory(clientHttpRequestFactory); +final RequestFactory requestFactory = new SpringRequestFactory(clientHttpRequestFactory, ContentEncoding.GZIP); -final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI) - .withRequestFactory(requestFactory) +final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, requestFactory) .withAccessTokenProvider(new ZignAccessTokenProvider()) .build(); @@ -254,14 +253,14 @@ Fahrschein handles content compression transparently to the API consumer, and mo ### Consuming -For event consumption the underlying HTTP client implementations send `Accept-Encoding` headers indicating the supported compression algorithm. At the time of writing, the default settings for all tested client implementations support `gzip` compression. - -If this is undesired, please add the following header to your request: `Accept-Encoding: identity`. +For event consumption the underlying HTTP client implementations send `Accept-Encoding` headers, indicating their supported compression algorithms. +At the time of writing, all tested client implementations default to `gzip` compression. ### Publishing -For event publishing, the `Request` body gets gzip-encoded by Fahrschein. To disable compression of the POST body, you need to call `disableContentCompression()` on your `RequestFactory`. - +For event publishing, the `Request` body can also get gzip-encoded by Fahrschein, if enabled when building the RequestFactory. +For this, you need to pass `ContentEncoding.GZIP`, or if compression is undesired, pass `ContentEncoding.IDENTITY`. +In the future, we may support other encoding formats, like Zstandard. ## Fahrschein compared to other Nakadi client libraries diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java index de5e2f91..232412fe 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java @@ -15,8 +15,6 @@ import java.util.List; import java.util.zip.GZIPOutputStream; -import static org.zalando.fahrschein.http.api.ContentEncoding.GZIP; - /** * {@link Request} implementation that uses standard JDK facilities to * execute buffered requests. Created via the {@link SimpleRequestFactory}. @@ -110,7 +108,7 @@ public final OutputStream getBody() throws IOException { assertNotExecuted(); if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); - if (this.connection.getDoOutput() && GZIP.equals(this.contentEncoding)) { + if (this.connection.getDoOutput() && ContentEncoding.GZIP.equals(this.contentEncoding)) { this.connection.setRequestProperty("Content-Encoding", this.contentEncoding.getEncoding()); return new GZIPOutputStream(this.bufferedOutput); } From 0881429651b5b2405c4a099864f7767dcf4b9998 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 22 Mar 2022 13:16:42 +0100 Subject: [PATCH 08/14] Use idiomatic header helper from the libs --- .../zalando/fahrschein/http/apache/HttpComponentsRequest.java | 3 ++- .../org/zalando/fahrschein/http/spring/RequestAdapter.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java index 88148623..417350d0 100644 --- a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java +++ b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java @@ -2,6 +2,7 @@ import org.apache.http.HttpEntity; import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpHeaders; import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpUriRequest; @@ -101,7 +102,7 @@ public final OutputStream getBody() throws IOException { if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); if (writeMethods.contains(getMethod()) && ContentEncoding.GZIP.equals(this.contentEncoding)) { - this.httpRequest.setHeader("Content-Encoding", this.contentEncoding.getEncoding()); + this.httpRequest.setHeader(HttpHeaders.CONTENT_ENCODING, this.contentEncoding.getEncoding()); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java index 46498791..fb433a9c 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java @@ -1,6 +1,6 @@ package org.zalando.fahrschein.http.spring; -import org.springframework.http.HttpMethod; +import org.springframework.http.HttpHeaders; import org.springframework.http.client.ClientHttpRequest; import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.Headers; @@ -43,7 +43,7 @@ public Headers getHeaders() { @Override public OutputStream getBody() throws IOException { if (writeMethods.contains(getMethod()) && ContentEncoding.GZIP.equals(contentEncoding)) { - clientHttpRequest.getHeaders().set("Content-Encoding", contentEncoding.getEncoding()); + clientHttpRequest.getHeaders().set(HttpHeaders.CONTENT_ENCODING, contentEncoding.getEncoding()); return new GZIPOutputStream(clientHttpRequest.getBody()); } return clientHttpRequest.getBody(); From f0afa03afab26d92bafc8de7e709f901c6315550 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 22 Mar 2022 13:52:38 +0100 Subject: [PATCH 09/14] Add an example implementation how to disable content-compression of server response --- README.md | 3 +- .../http/apache/ApacheNakadiClientTest.java | 28 +---------- ...dentityEncodingApacheNakadiClientTest.java | 18 +++++++ ...dentityEncodingSimpleNakadiClientTest.java | 13 +++++ ...dentityEncodingSpringNakadiClientTest.java | 33 +++++++++++++ .../http/spring/SpringNakadiClientTest.java | 11 +---- .../http/apache/HttpComponentsRequest.java | 2 +- .../fahrschein/http/api/ContentEncoding.java | 10 ++-- .../http/simple/SimpleBufferingRequest.java | 2 +- .../http/spring/RequestAdapter.java | 2 +- .../IdentityAcceptEncodingRequestFactory.java | 23 +++++++++ ...ntityAcceptEncodingRequestFactoryTest.java | 48 +++++++++++++++++++ 12 files changed, 149 insertions(+), 44 deletions(-) create mode 100644 fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/IdentityEncodingApacheNakadiClientTest.java create mode 100644 fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/IdentityEncodingSimpleNakadiClientTest.java create mode 100644 fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/IdentityEncodingSpringNakadiClientTest.java create mode 100644 fahrschein/src/main/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactory.java create mode 100644 fahrschein/src/test/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactoryTest.java diff --git a/README.md b/README.md index 5c328496..b2041f35 100644 --- a/README.md +++ b/README.md @@ -254,7 +254,8 @@ Fahrschein handles content compression transparently to the API consumer, and mo ### Consuming For event consumption the underlying HTTP client implementations send `Accept-Encoding` headers, indicating their supported compression algorithms. -At the time of writing, all tested client implementations default to `gzip` compression. +At the time of writing, all tested client implementations default to `gzip` compression. If this is undesired, wrap your +RequestFactory into a `IdentityAcceptEncodingRequestFactory`, which sets the `Accept-Encoding` header to `identity`. ### Publishing diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java index 0a18cadd..39572a4c 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/ApacheNakadiClientTest.java @@ -1,40 +1,16 @@ package org.zalando.fahrschein.http.apache; -import org.zalando.fahrschein.http.AbstractRequestFactoryTest; -import org.apache.http.client.config.RequestConfig; -import org.apache.http.config.ConnectionConfig; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.zalando.fahrschein.http.AbstractRequestFactoryTest; import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.RequestFactory; -import java.util.concurrent.TimeUnit; - public class ApacheNakadiClientTest extends AbstractRequestFactoryTest { @Override protected RequestFactory getRequestFactory() { - final RequestConfig requestConfig = RequestConfig.custom() - .setSocketTimeout(60000) - .setConnectTimeout(2000) - .setConnectionRequestTimeout(8000) - .setContentCompressionEnabled(false) - .build(); - - final ConnectionConfig connectionConfig = ConnectionConfig.custom() - .setBufferSize(512) - .build(); - - final CloseableHttpClient httpClient = HttpClients.custom() - .setDefaultRequestConfig(requestConfig) - .setDefaultConnectionConfig(connectionConfig) - .setConnectionTimeToLive(30, TimeUnit.SECONDS) - .disableAutomaticRetries() - .disableRedirectHandling() - .setMaxConnTotal(8) - .setMaxConnPerRoute(2) - .build(); - + final CloseableHttpClient httpClient = HttpClients.createDefault(); return new HttpComponentsRequestFactory(httpClient, ContentEncoding.GZIP); } diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/IdentityEncodingApacheNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/IdentityEncodingApacheNakadiClientTest.java new file mode 100644 index 00000000..4490c8ac --- /dev/null +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/apache/IdentityEncodingApacheNakadiClientTest.java @@ -0,0 +1,18 @@ +package org.zalando.fahrschein.http.apache; + +import org.zalando.fahrschein.IdentityAcceptEncodingRequestFactory; +import org.zalando.fahrschein.http.AbstractRequestFactoryTest; +import org.zalando.fahrschein.http.api.ContentEncoding; +import org.zalando.fahrschein.http.api.RequestFactory; + +import static org.apache.http.impl.client.HttpClients.*; + +public class IdentityEncodingApacheNakadiClientTest extends AbstractRequestFactoryTest { + + @Override + protected RequestFactory getRequestFactory() { + + return new IdentityAcceptEncodingRequestFactory(new HttpComponentsRequestFactory(createMinimal(), ContentEncoding.IDENTITY)); + } + +} diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/IdentityEncodingSimpleNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/IdentityEncodingSimpleNakadiClientTest.java new file mode 100644 index 00000000..2cb76075 --- /dev/null +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/simple/IdentityEncodingSimpleNakadiClientTest.java @@ -0,0 +1,13 @@ +package org.zalando.fahrschein.http.simple; + +import org.zalando.fahrschein.IdentityAcceptEncodingRequestFactory; +import org.zalando.fahrschein.http.AbstractRequestFactoryTest; +import org.zalando.fahrschein.http.api.ContentEncoding; +import org.zalando.fahrschein.http.api.RequestFactory; + +public class IdentityEncodingSimpleNakadiClientTest extends AbstractRequestFactoryTest { + @Override + protected RequestFactory getRequestFactory() { + return new IdentityAcceptEncodingRequestFactory(new SimpleRequestFactory(ContentEncoding.IDENTITY)); + } +} diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/IdentityEncodingSpringNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/IdentityEncodingSpringNakadiClientTest.java new file mode 100644 index 00000000..f1f16d33 --- /dev/null +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/IdentityEncodingSpringNakadiClientTest.java @@ -0,0 +1,33 @@ +package org.zalando.fahrschein.http.spring; + +import okhttp3.OkHttpClient; +import okhttp3.logging.HttpLoggingInterceptor; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.zalando.fahrschein.IdentityAcceptEncodingRequestFactory; +import org.zalando.fahrschein.http.AbstractRequestFactoryTest; +import org.zalando.fahrschein.http.api.ContentEncoding; +import org.zalando.fahrschein.http.api.RequestFactory; + +import java.util.concurrent.TimeUnit; + +public class IdentityEncodingSpringNakadiClientTest extends AbstractRequestFactoryTest { + + private static final Logger logger = LoggerFactory.getLogger("okhttp3.wire"); + private static final HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(logger::debug); + static { + loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); + } + + @Override + protected RequestFactory getRequestFactory() { + final OkHttpClient client = new OkHttpClient.Builder() + .addInterceptor(loggingInterceptor) + .build(); + + final OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); + return new IdentityAcceptEncodingRequestFactory(new SpringRequestFactory(clientHttpRequestFactory, ContentEncoding.IDENTITY)); + } + +} diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java index fa49a6ad..fcbfa2b3 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/spring/SpringNakadiClientTest.java @@ -1,17 +1,14 @@ package org.zalando.fahrschein.http.spring; +import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.zalando.fahrschein.http.AbstractRequestFactoryTest; -import okhttp3.ConnectionPool; -import okhttp3.OkHttpClient; import org.springframework.http.client.OkHttp3ClientHttpRequestFactory; +import org.zalando.fahrschein.http.AbstractRequestFactoryTest; import org.zalando.fahrschein.http.api.ContentEncoding; import org.zalando.fahrschein.http.api.RequestFactory; -import java.util.concurrent.TimeUnit; - public class SpringNakadiClientTest extends AbstractRequestFactoryTest { private static final Logger logger = LoggerFactory.getLogger("okhttp3.wire"); @@ -24,10 +21,6 @@ public class SpringNakadiClientTest extends AbstractRequestFactoryTest { protected RequestFactory getRequestFactory() { final OkHttpClient client = new OkHttpClient.Builder() .addInterceptor(loggingInterceptor) - .readTimeout(60, TimeUnit.SECONDS) - .connectTimeout(2, TimeUnit.SECONDS) - .writeTimeout(10, TimeUnit.SECONDS) - .connectionPool(new ConnectionPool(2, 5*60, TimeUnit.SECONDS)) .build(); final OkHttp3ClientHttpRequestFactory clientHttpRequestFactory = new OkHttp3ClientHttpRequestFactory(client); diff --git a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java index 417350d0..b4f3bb41 100644 --- a/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java +++ b/fahrschein-http-apache/src/main/java/org/zalando/fahrschein/http/apache/HttpComponentsRequest.java @@ -102,7 +102,7 @@ public final OutputStream getBody() throws IOException { if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); if (writeMethods.contains(getMethod()) && ContentEncoding.GZIP.equals(this.contentEncoding)) { - this.httpRequest.setHeader(HttpHeaders.CONTENT_ENCODING, this.contentEncoding.getEncoding()); + this.httpRequest.setHeader(HttpHeaders.CONTENT_ENCODING, this.contentEncoding.value()); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java index 5fe29645..1d39e3d1 100644 --- a/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java +++ b/fahrschein-http-api/src/main/java/org/zalando/fahrschein/http/api/ContentEncoding.java @@ -3,13 +3,13 @@ public enum ContentEncoding { IDENTITY("identity"), GZIP("gzip"); - private final String encoding; + private final String value; - ContentEncoding(String encoding) { - this.encoding = encoding; + ContentEncoding(String value) { + this.value = value; } - public String getEncoding() { - return encoding; + public String value() { + return value; } } diff --git a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java index 232412fe..4b73654c 100644 --- a/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java +++ b/fahrschein-http-simple/src/main/java/org/zalando/fahrschein/http/simple/SimpleBufferingRequest.java @@ -109,7 +109,7 @@ public final OutputStream getBody() throws IOException { if (this.bufferedOutput == null) { this.bufferedOutput = new ByteArrayOutputStream(1024); if (this.connection.getDoOutput() && ContentEncoding.GZIP.equals(this.contentEncoding)) { - this.connection.setRequestProperty("Content-Encoding", this.contentEncoding.getEncoding()); + this.connection.setRequestProperty("Content-Encoding", this.contentEncoding.value()); return new GZIPOutputStream(this.bufferedOutput); } } diff --git a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java index fb433a9c..7119dbc8 100644 --- a/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java +++ b/fahrschein-http-spring/src/main/java/org/zalando/fahrschein/http/spring/RequestAdapter.java @@ -43,7 +43,7 @@ public Headers getHeaders() { @Override public OutputStream getBody() throws IOException { if (writeMethods.contains(getMethod()) && ContentEncoding.GZIP.equals(contentEncoding)) { - clientHttpRequest.getHeaders().set(HttpHeaders.CONTENT_ENCODING, contentEncoding.getEncoding()); + clientHttpRequest.getHeaders().set(HttpHeaders.CONTENT_ENCODING, contentEncoding.value()); return new GZIPOutputStream(clientHttpRequest.getBody()); } return clientHttpRequest.getBody(); diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactory.java b/fahrschein/src/main/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactory.java new file mode 100644 index 00000000..60ac148f --- /dev/null +++ b/fahrschein/src/main/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactory.java @@ -0,0 +1,23 @@ +package org.zalando.fahrschein; + +import org.zalando.fahrschein.http.api.Request; +import org.zalando.fahrschein.http.api.RequestFactory; + +import java.io.IOException; +import java.net.URI; + +public class IdentityAcceptEncodingRequestFactory implements RequestFactory { + + private final RequestFactory delegate; + + public IdentityAcceptEncodingRequestFactory(RequestFactory delegate) { + this.delegate = delegate; + } + + @Override + public Request createRequest(URI uri, String method) throws IOException { + Request request = delegate.createRequest(uri, method); + request.getHeaders().put("Accept-Encoding", "identity"); + return request; + } +} diff --git a/fahrschein/src/test/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactoryTest.java b/fahrschein/src/test/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactoryTest.java new file mode 100644 index 00000000..8f80cdf7 --- /dev/null +++ b/fahrschein/src/test/java/org/zalando/fahrschein/IdentityAcceptEncodingRequestFactoryTest.java @@ -0,0 +1,48 @@ +package org.zalando.fahrschein; + +import org.junit.Test; +import org.mockito.Mockito; +import org.zalando.fahrschein.http.api.Headers; +import org.zalando.fahrschein.http.api.HeadersImpl; +import org.zalando.fahrschein.http.api.Request; +import org.zalando.fahrschein.http.api.RequestFactory; + +import java.io.IOException; +import java.net.URI; +import java.util.Arrays; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.when; + +public class IdentityAcceptEncodingRequestFactoryTest { + + private final Request request = Mockito.mock(Request.class); + private final RequestFactory delegate = Mockito.mock(RequestFactory.class); + + @Test + public void shouldSetAcceptEncodingHeader() throws IOException { + final Headers headers = new HeadersImpl(); + when(request.getHeaders()).thenReturn(headers); + when(delegate.createRequest(any(URI.class), any(String.class))).thenReturn(request); + + IdentityAcceptEncodingRequestFactory SUT = new IdentityAcceptEncodingRequestFactory(delegate); + SUT.createRequest(URI.create("any://uri"), "GET"); + + assertEquals(Arrays.asList("identity"), headers.get("Accept-Encoding")); + } + + @Test + public void shouldOverrideExistingAcceptEncodingHeader() throws IOException { + final Headers headers = new HeadersImpl(); + headers.put("Accept-Encoding", "gzip"); + when(request.getHeaders()).thenReturn(headers); + when(delegate.createRequest(any(URI.class), any(String.class))).thenReturn(request); + + IdentityAcceptEncodingRequestFactory SUT = new IdentityAcceptEncodingRequestFactory(delegate); + SUT.createRequest(URI.create("any://uri"), "GET"); + + assertEquals(Arrays.asList("identity"), headers.get("Accept-Encoding")); + } + +} From 1ba64b5ff92ff8796c7c72f0c174e17b286a2236 Mon Sep 17 00:00:00 2001 From: Oliver Date: Tue, 22 Mar 2022 17:21:22 +0100 Subject: [PATCH 10/14] pom cleanup --- fahrschein-http-apache/pom.xml | 13 +------------ fahrschein-http-api/pom.xml | 13 +------------ fahrschein-http-spring/pom.xml | 13 +------------ 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/fahrschein-http-apache/pom.xml b/fahrschein-http-apache/pom.xml index d1bf9c06..4604b59f 100644 --- a/fahrschein-http-apache/pom.xml +++ b/fahrschein-http-apache/pom.xml @@ -14,17 +14,6 @@ fahrschein-http-apache Fahrschein HTTP Client using Apache HttpComponents - - - - maven-surefire-plugin - - false - - - - - org.zalando @@ -50,4 +39,4 @@ - \ No newline at end of file + diff --git a/fahrschein-http-api/pom.xml b/fahrschein-http-api/pom.xml index c8a6371b..d8c132d0 100644 --- a/fahrschein-http-api/pom.xml +++ b/fahrschein-http-api/pom.xml @@ -14,17 +14,6 @@ fahrschein-http-api Fahrschein HTTP API - - - - maven-surefire-plugin - - false - - - - - com.google.code.findbugs @@ -40,4 +29,4 @@ - \ No newline at end of file + diff --git a/fahrschein-http-spring/pom.xml b/fahrschein-http-spring/pom.xml index 4b5d85ba..49318e76 100644 --- a/fahrschein-http-spring/pom.xml +++ b/fahrschein-http-spring/pom.xml @@ -14,17 +14,6 @@ fahrschein-http-spring Fahrschein HTTP Spring Adapter - - - - maven-surefire-plugin - - false - - - - - org.zalando @@ -76,4 +65,4 @@ - \ No newline at end of file + From 6a6baab1a879aa41d94947e7dc33c30dd0e55c8a Mon Sep 17 00:00:00 2001 From: Oliver Date: Wed, 23 Mar 2022 16:24:12 +0100 Subject: [PATCH 11/14] testing: switch to explicit versions in docker-compose --- fahrschein-e2e-test/src/test/resources/docker-compose.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fahrschein-e2e-test/src/test/resources/docker-compose.yaml b/fahrschein-e2e-test/src/test/resources/docker-compose.yaml index b1939175..3510d3be 100644 --- a/fahrschein-e2e-test/src/test/resources/docker-compose.yaml +++ b/fahrschein-e2e-test/src/test/resources/docker-compose.yaml @@ -1,7 +1,7 @@ version: '3' services: nakadi-ui: - image: nakadi/nakadi-ui:latest + image: nakadi/nakadi-ui:master-292 ports: - 3000 depends_on: @@ -10,7 +10,7 @@ services: - NAKADI_API_URL=http://nakadi:8080 nakadi: - image: adyach/nakadi-docker:latest + image: adyach/nakadi-docker:3.3.8 ports: - 8080:8080 - 7979 @@ -25,7 +25,7 @@ services: - SPRING_DATASOURCE_URL=jdbc:postgresql://nakadi_postgres:5432/local_nakadi_db nakadi_postgres: - image: adyach/nakadi-postgres:latest + image: adyach/nakadi-postgres:3.2.4 ports: - 5432 environment: From 9f1707ffa641e1ac2c97666d0e224af057e12508 Mon Sep 17 00:00:00 2001 From: Oliver Trosien Date: Fri, 25 Mar 2022 09:47:05 +0100 Subject: [PATCH 12/14] Applying recommended logging from testcontainers https://www.testcontainers.org/supported_docker_environment/logging_config/ --- fahrschein-e2e-test/src/test/resources/log4j2.xml | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/fahrschein-e2e-test/src/test/resources/log4j2.xml b/fahrschein-e2e-test/src/test/resources/log4j2.xml index 8afed281..e765ab35 100644 --- a/fahrschein-e2e-test/src/test/resources/log4j2.xml +++ b/fahrschein-e2e-test/src/test/resources/log4j2.xml @@ -9,14 +9,20 @@ + + + + + + + From 55dc2ca92491484a0442d86be5f0d90d97281556 Mon Sep 17 00:00:00 2001 From: Oliver Trosien Date: Fri, 25 Mar 2022 10:00:01 +0100 Subject: [PATCH 13/14] Final cleanups: * Wait for 3minutes for docker-compose to start up. In Github it seems to take quite long sometimes * Don't start nakadi-ui * Fix the description of automatically enabling compression in README --- README.md | 4 +++- .../e2e/NakadiTestWithDockerCompose.java | 5 ++++- .../src/test/resources/docker-compose.yaml | 16 ++++++++-------- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index b2041f35..140db9c6 100644 --- a/README.md +++ b/README.md @@ -249,7 +249,9 @@ final NakadiClient nakadiClient = NakadiClient.builder(NAKADI_URI, requestFactor ## Content-Compression -Fahrschein handles content compression transparently to the API consumer, and mostly independently of the actual HTTP client implementation. Since version `0.20.0` it is enabled by default, to both compress HTTP POST bodies for event-publishing, as well as requesting compression from the server when consuming events. +Fahrschein handles content compression transparently to the API consumer, and mostly independently of the actual HTTP +client implementation. Since version `0.20.0` it can be enabled to both compress HTTP POST bodies when event +publishing, and requesting payload compression from Nakadi when consuming events. ### Consuming diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/e2e/NakadiTestWithDockerCompose.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/e2e/NakadiTestWithDockerCompose.java index d77b0c73..82c30cad 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/e2e/NakadiTestWithDockerCompose.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/e2e/NakadiTestWithDockerCompose.java @@ -12,6 +12,7 @@ import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.output.OutputFrame; import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.containers.wait.strategy.Wait; import java.io.File; import java.io.IOException; @@ -20,6 +21,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.time.Duration; import java.util.function.Consumer; import java.util.stream.Stream; @@ -42,7 +44,8 @@ public abstract class NakadiTestWithDockerCompose { .withExposedService("nakadi_postgres_1", 5432) .withExposedService("zookeeper_1", 2181) .withExposedService("kafka_1", 9092) - .withExposedService("nakadi_1", 8080) + .withExposedService("nakadi_1", 8080, + Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(180))) .withLogConsumer("nakadi_1", LOG_CONSUMER); compose.start(); } diff --git a/fahrschein-e2e-test/src/test/resources/docker-compose.yaml b/fahrschein-e2e-test/src/test/resources/docker-compose.yaml index 3510d3be..525370d7 100644 --- a/fahrschein-e2e-test/src/test/resources/docker-compose.yaml +++ b/fahrschein-e2e-test/src/test/resources/docker-compose.yaml @@ -1,13 +1,13 @@ version: '3' services: - nakadi-ui: - image: nakadi/nakadi-ui:master-292 - ports: - - 3000 - depends_on: - - nakadi - environment: - - NAKADI_API_URL=http://nakadi:8080 +# nakadi-ui: +# image: nakadi/nakadi-ui:master-292 +# ports: +# - 3000 +# depends_on: +# - nakadi +# environment: +# - NAKADI_API_URL=http://nakadi:8080 nakadi: image: adyach/nakadi-docker:3.3.8 From d5cb1a5da4edf6ccf1ad734593d5a9faa9da99cf Mon Sep 17 00:00:00 2001 From: Oliver Trosien Date: Mon, 28 Mar 2022 09:48:05 +0200 Subject: [PATCH 14/14] Upgrade to the latest nakadi-docker builds --- .../http/AbstractRequestFactoryTest.java | 4 ++++ .../src/test/resources/docker-compose.yaml | 8 ++++---- .../zalando/fahrschein/domain/Metadata.java | 19 +++++++++++++------ .../fahrschein/domain/MetadataTest.java | 4 +++- 4 files changed, 24 insertions(+), 11 deletions(-) diff --git a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java index bcadf072..1a8d7b64 100644 --- a/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java +++ b/fahrschein-e2e-test/src/test/java/org/zalando/fahrschein/http/AbstractRequestFactoryTest.java @@ -1,6 +1,7 @@ package org.zalando.fahrschein.http; import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.DeserializationFeature; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.PropertyNamingStrategy; import com.fasterxml.jackson.databind.SerializationFeature; @@ -34,6 +35,9 @@ public abstract class AbstractRequestFactoryTest extends NakadiTestWithDockerCom objectMapper.registerModule(new Jdk8Module()); objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + // After a Nakadi-Docker upgrade, check if the response metadata changed + // by enabling deserialization failure on unknown properties. + objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES); objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); } diff --git a/fahrschein-e2e-test/src/test/resources/docker-compose.yaml b/fahrschein-e2e-test/src/test/resources/docker-compose.yaml index 525370d7..4c0ebf24 100644 --- a/fahrschein-e2e-test/src/test/resources/docker-compose.yaml +++ b/fahrschein-e2e-test/src/test/resources/docker-compose.yaml @@ -10,7 +10,7 @@ services: # - NAKADI_API_URL=http://nakadi:8080 nakadi: - image: adyach/nakadi-docker:3.3.8 + image: ghcr.io/adyach/nakadi-docker/nakadi:3.4 ports: - 8080:8080 - 7979 @@ -25,7 +25,7 @@ services: - SPRING_DATASOURCE_URL=jdbc:postgresql://nakadi_postgres:5432/local_nakadi_db nakadi_postgres: - image: adyach/nakadi-postgres:3.2.4 + image: ghcr.io/adyach/nakadi-docker/postgres:13.6 ports: - 5432 environment: @@ -34,12 +34,12 @@ services: POSTGRES_DB: local_nakadi_db zookeeper: - image: wurstmeister/zookeeper:3.4.6 + image: ghcr.io/adyach/nakadi-docker/zookeeper:3.6.3 ports: - 2181 kafka: - image: wurstmeister/kafka:2.12-2.5.0 + image: ghcr.io/adyach/kafka-docker/kafka-docker:2.7.0 ports: - 9092 depends_on: diff --git a/fahrschein/src/main/java/org/zalando/fahrschein/domain/Metadata.java b/fahrschein/src/main/java/org/zalando/fahrschein/domain/Metadata.java index 5223c0f7..9ab54c09 100644 --- a/fahrschein/src/main/java/org/zalando/fahrschein/domain/Metadata.java +++ b/fahrschein/src/main/java/org/zalando/fahrschein/domain/Metadata.java @@ -15,6 +15,7 @@ public final class Metadata { private final String eid; private final String partition; private final String version; + private final String publishedBy; private final OffsetDateTime occurredAt; private final OffsetDateTime receivedAt; private final String flowId; @@ -22,27 +23,28 @@ public final class Metadata { @JsonCreator @Deprecated - private Metadata(@JsonProperty("event_type") String eventType, @JsonProperty("eid") String eid, @JsonProperty("occurred_at") String occurredAt, @JsonProperty("partition") String partition, @JsonProperty("version") String version, @JsonProperty("received_at") String receivedAt, @JsonProperty("flow_id") String flowId, @JsonProperty("span_ctx") Map spanCtx) { - this(eventType, eid, occurredAt == null ? null : OffsetDateTime.parse(occurredAt), partition, version, receivedAt == null ? null : OffsetDateTime.parse(receivedAt), flowId, spanCtx); + private Metadata(@JsonProperty("event_type") String eventType, @JsonProperty("eid") String eid, @JsonProperty("occurred_at") String occurredAt, @JsonProperty("partition") String partition, @JsonProperty("version") String version, @JsonProperty("published_by") String publishedBy, @JsonProperty("received_at") String receivedAt, @JsonProperty("flow_id") String flowId, @JsonProperty("span_ctx") Map spanCtx) { + this(eventType, eid, occurredAt == null ? null : OffsetDateTime.parse(occurredAt), partition, version, publishedBy, receivedAt == null ? null : OffsetDateTime.parse(receivedAt), flowId, spanCtx); } - public Metadata(String eventType, String eid, OffsetDateTime occurredAt, String partition, String version, OffsetDateTime receivedAt, String flowId) { - this(eventType, eid, occurredAt, partition, version, receivedAt, flowId, null); + public Metadata(String eventType, String eid, OffsetDateTime occurredAt, String partition, String version, String publishedBy, OffsetDateTime receivedAt, String flowId) { + this(eventType, eid, occurredAt, partition, version, publishedBy, receivedAt, flowId, null); } - public Metadata(String eventType, String eid, OffsetDateTime occurredAt, String partition, String version, OffsetDateTime receivedAt, String flowId, Map spanCtx) { + public Metadata(String eventType, String eid, OffsetDateTime occurredAt, String partition, String version, String publishedBy, OffsetDateTime receivedAt, String flowId, Map spanCtx) { this.eventType = eventType; this.eid = eid; this.occurredAt = occurredAt; this.partition = partition; this.version = version; + this.publishedBy = publishedBy; this.receivedAt = receivedAt; this.flowId = flowId; this.spanCtx = spanCtx == null ? Collections.emptyMap() : Collections.unmodifiableMap(new LinkedHashMap<>(spanCtx)); } public Metadata(String eid, OffsetDateTime occurredAt) { - this(null, eid, occurredAt, null, null, null, null, null); + this(null, eid, occurredAt, null, null, null, null, null, null); } public String getEventType() { @@ -73,6 +75,10 @@ public Map getSpanCtx() { return spanCtx; } + public String getPublishedBy() { + return publishedBy; + } + @Override public String toString() { return "Metadata{" + @@ -81,6 +87,7 @@ public String toString() { ", occurredAt=" + occurredAt + ", partition='" + partition + '\'' + ", version='" + version + '\'' + + ", publishedBy='" + publishedBy + '\'' + ", receivedAt=" + receivedAt + ", flowId='" + flowId + '\'' + '}'; diff --git a/fahrschein/src/test/java/org/zalando/fahrschein/domain/MetadataTest.java b/fahrschein/src/test/java/org/zalando/fahrschein/domain/MetadataTest.java index 2cf345b0..735ef43a 100644 --- a/fahrschein/src/test/java/org/zalando/fahrschein/domain/MetadataTest.java +++ b/fahrschein/src/test/java/org/zalando/fahrschein/domain/MetadataTest.java @@ -15,17 +15,19 @@ public void shouldHaveCleanStringRepresentation() { final String eid = "a3e25946-5ae9-3964-91fa-26ecb7588d67"; final String partition = "partition1"; final String version = "v1"; + final String publishedBy = "unauthenticated"; final OffsetDateTime occurredAt = OffsetDateTime.now(); final OffsetDateTime receivedAt = OffsetDateTime.now(); final String flowId = UUID.randomUUID().toString(); - final Metadata metadata = new Metadata(eventType, eid, occurredAt, partition, version, receivedAt, flowId, Collections.emptyMap()); + final Metadata metadata = new Metadata(eventType, eid, occurredAt, partition, version, publishedBy, receivedAt, flowId, Collections.emptyMap()); Assert.assertTrue(metadata.toString().contains(metadata.getEventType())); Assert.assertTrue(metadata.toString().contains(metadata.getEid())); Assert.assertTrue(metadata.toString().contains(metadata.getOccurredAt().toString())); Assert.assertTrue(metadata.toString().contains(metadata.getPartition())); Assert.assertTrue(metadata.toString().contains(metadata.getVersion())); + Assert.assertTrue(metadata.toString().contains(metadata.getPublishedBy())); Assert.assertTrue(metadata.toString().contains(metadata.getReceivedAt().toString())); Assert.assertTrue(metadata.toString().contains(metadata.getFlowId())); }