From 9cdf25548ab13b62a38b26b469878bcc5dd1cd5e Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 22 Apr 2021 13:50:07 +1200 Subject: [PATCH 1/2] #5 - Add HttpClientRequest.url(String url) - ability to replace the base url --- .../java/io/avaje/http/client/BodyReader.java | 6 ++ .../avaje/http/client/DHttpClientRequest.java | 6 ++ .../avaje/http/client/HttpClientContext.java | 66 ++++++++++++++++++- .../avaje/http/client/HttpClientRequest.java | 27 ++++++++ .../java/io/avaje/http/client/UrlBuilder.java | 37 ++++++++++- .../io/avaje/http/client/package-info.java | 22 +++++++ .../http/client/HelloControllerTest.java | 12 ++++ .../io/avaje/http/client/UrlBuilderTest.java | 6 ++ 8 files changed, 180 insertions(+), 2 deletions(-) create mode 100644 client/src/main/java/io/avaje/http/client/package-info.java diff --git a/client/src/main/java/io/avaje/http/client/BodyReader.java b/client/src/main/java/io/avaje/http/client/BodyReader.java index e7faf38..2bfe4a2 100644 --- a/client/src/main/java/io/avaje/http/client/BodyReader.java +++ b/client/src/main/java/io/avaje/http/client/BodyReader.java @@ -1,7 +1,13 @@ package io.avaje.http.client; +/** + * Read content as a java type. + */ public interface BodyReader { + /** + * Read the content returning it as a java type. + */ T read(BodyContent content); } diff --git a/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java b/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java index 668279e..4d60d4b 100644 --- a/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java +++ b/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java @@ -75,6 +75,12 @@ public HttpClientRequest gzip(boolean gzip) { return this; } + @Override + public HttpClientRequest url(String baseUrl) { + url.url(baseUrl); + return this; + } + @Override public HttpClientRequest path(String path) { url.path(path); diff --git a/client/src/main/java/io/avaje/http/client/HttpClientContext.java b/client/src/main/java/io/avaje/http/client/HttpClientContext.java index 7fce52a..6d16128 100644 --- a/client/src/main/java/io/avaje/http/client/HttpClientContext.java +++ b/client/src/main/java/io/avaje/http/client/HttpClientContext.java @@ -8,11 +8,40 @@ /** * The HTTP client context that we use to build and process requests. + * + *
{@code
+ *
+ *   HttpClientContext ctx = HttpClientContext.newBuilder()
+ *       .withBaseUrl("http://localhost:8080")
+ *       .withBodyAdapter(new JacksonBodyAdapter(new ObjectMapper()))
+ *       .build();
+ *
+ *  HelloDto dto = ctx.request()
+ *       .path("hello")
+ *       .queryParam("name", "Rob")
+ *       .queryParam("say", "Ki ora")
+ *       .get()
+ *       .bean(HelloDto.class);
+ *
+ * }
*/ public interface HttpClientContext { /** * Return the builder to config and build the client context. + * + *
{@code
+   *
+   *   HttpClientContext ctx = HttpClientContext.newBuilder()
+   *       .withBaseUrl("http://localhost:8080")
+   *       .withBodyAdapter(new JacksonBodyAdapter(new ObjectMapper()))
+   *       .build();
+   *
+   *  HttpResponse res = ctx.request()
+   *       .path("hello")
+   *       .get().asString();
+   *
+   * }
*/ static HttpClientContext.Builder newBuilder() { return new DHttpClientContextBuilder(); @@ -31,6 +60,9 @@ static HttpClientContext.Builder newBuilder() { /** * Return the body adapter used by the client context. + *

+ * This is the body adapter used to convert request and response + * bodies to java types. For example using Jackson with JSON payloads. */ BodyAdapter converters(); @@ -70,9 +102,24 @@ static HttpClientContext.Builder newBuilder() { */ byte[] decodeContent(String encoding, byte[] content); - /** * Builds the HttpClientContext. + * + *

{@code
+   *
+   *   HttpClientContext ctx = HttpClientContext.newBuilder()
+   *       .withBaseUrl("http://localhost:8080")
+   *       .withBodyAdapter(new JacksonBodyAdapter(new ObjectMapper()))
+   *       .build();
+   *
+   *  HelloDto dto = ctx.request()
+   *       .path("hello")
+   *       .queryParam("name", "Rob")
+   *       .queryParam("say", "Ki ora")
+   *       .get()
+   *       .bean(HelloDto.class);
+   *
+   * }
*/ interface Builder { @@ -85,6 +132,8 @@ interface Builder { /** * Set the base URL to use for requests created from the context. + *

+ * Note that the base url can be replaced via {@link HttpClientRequest#url(String)}. */ Builder withBaseUrl(String baseUrl); @@ -139,6 +188,21 @@ interface Builder { /** * Build and return the context. + * + *

{@code
+     *
+     *   HttpClientContext ctx = HttpClientContext.newBuilder()
+     *       .withBaseUrl("http://localhost:8080")
+     *       .withBodyAdapter(new JacksonBodyAdapter(new ObjectMapper()))
+     *       .build();
+     *
+     *  HelloDto dto = ctx.request()
+     *       .path("hello")
+     *       .queryParam("say", "Ki ora")
+     *       .get()
+     *       .bean(HelloDto.class);
+     *
+     * }
*/ HttpClientContext build(); } diff --git a/client/src/main/java/io/avaje/http/client/HttpClientRequest.java b/client/src/main/java/io/avaje/http/client/HttpClientRequest.java index e860425..8e0820f 100644 --- a/client/src/main/java/io/avaje/http/client/HttpClientRequest.java +++ b/client/src/main/java/io/avaje/http/client/HttpClientRequest.java @@ -13,6 +13,16 @@ * Largely wraps the standard JDK HttpRequest with additional * support for converting beans to body content and converting * beans from response content. + * + *
{@code
+ *
+ *  HelloDto dto = clientContext.request()
+ *       .path("hello").queryParam("name", "Rob").queryParam("say", "Ki ora")
+ *       .get().bean(HelloDto.class);
+ *
+ * }
+ * + * @see HttpClientContext */ public interface HttpClientRequest { @@ -42,6 +52,23 @@ public interface HttpClientRequest { */ HttpClientRequest gzip(boolean gzip); + /** + * Set the URL to use replacing the base URL. + *
{code
+   *
+   *  HttpResponse res = clientContext.request()
+   *       .url("http://127.0.0.1:8887")
+   *       .path("hello")
+   *       .get().asString();
+   *
+   * }
+ * + * @param url The url effectively replacing the base url. + * @return The request being built + * @see HttpClientContext.Builder#withBaseUrl(String) + */ + HttpClientRequest url(String url); + /** * Add a path segment to the URL. * diff --git a/client/src/main/java/io/avaje/http/client/UrlBuilder.java b/client/src/main/java/io/avaje/http/client/UrlBuilder.java index 57f7c3f..07d1a0c 100644 --- a/client/src/main/java/io/avaje/http/client/UrlBuilder.java +++ b/client/src/main/java/io/avaje/http/client/UrlBuilder.java @@ -3,21 +3,46 @@ import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +/** + * Build a URL typically using a base url and adding path and query parameters. + */ public class UrlBuilder { private final StringBuilder buffer = new StringBuilder(100); private boolean hasParams; + /** + * Create with a base url. + */ public UrlBuilder(String base) { buffer.append(base); } + /** + * Set the url. This effectively replaces a base url. + */ + public UrlBuilder url(String url) { + buffer.delete(0, buffer.length()); + buffer.append(url); + return this; + } + + /** + * Add a path segment to the url. + *

+ * This includes appending a "/" prefix with the path. + */ public UrlBuilder path(String path) { buffer.append("/").append(path); return this; } + /** + * Append a query parameter. + *

+ * The name and value parameters are url encoded. + */ public UrlBuilder queryParam(String name, String value) { if (value != null) { buffer.append(hasParams ? '&' : '?'); @@ -27,6 +52,11 @@ public UrlBuilder queryParam(String name, String value) { return this; } + /** + * Append a matrix parameter. + *

+ * The name and value parameters are url encoded. + */ public UrlBuilder matrixParam(String name, String value) { if (value != null) { buffer.append(';').append(enc(name)).append("=").append(enc(value)); @@ -34,13 +64,18 @@ public UrlBuilder matrixParam(String name, String value) { return this; } + /** + * URL encode the value. + */ public static String enc(String val) { return URLEncoder.encode(val, StandardCharsets.UTF_8); } + /** + * Return the full URL. + */ public String build() { return buffer.toString(); } - } diff --git a/client/src/main/java/io/avaje/http/client/package-info.java b/client/src/main/java/io/avaje/http/client/package-info.java new file mode 100644 index 0000000..3ad25d4 --- /dev/null +++ b/client/src/main/java/io/avaje/http/client/package-info.java @@ -0,0 +1,22 @@ +/** + * Provides a HTTP client with support for adapting body content + * (like JSON) to java types. + *

+ * Uses the Java http client + * + *

{@code
+ *
+ *   HttpClientContext ctx = HttpClientContext.newBuilder()
+ *       .withBaseUrl("http://localhost:8080")
+ *       .withBodyAdapter(new JacksonBodyAdapter(new ObjectMapper()))
+ *       .build();
+ *
+ *  HelloDto dto = ctx.request()
+ *       .path("hello")
+ *       .queryParam("say", "Ki ora")
+ *       .get()
+ *       .bean(HelloDto.class);
+ *
+ * }
+ */ +package io.avaje.http.client; diff --git a/client/src/test/java/io/avaje/http/client/HelloControllerTest.java b/client/src/test/java/io/avaje/http/client/HelloControllerTest.java index 5c42d86..3881deb 100644 --- a/client/src/test/java/io/avaje/http/client/HelloControllerTest.java +++ b/client/src/test/java/io/avaje/http/client/HelloControllerTest.java @@ -28,6 +28,18 @@ void get_helloMessage() { assertThat(hres.statusCode()).isEqualTo(200); } + @Test + void get_helloMessage_via_url() { + + final HttpResponse hres = clientContext.request() + .url("http://127.0.0.1:8887") + .path("hello").path("message") + .get().asString(); + + assertThat(hres.body()).contains("hello world"); + assertThat(hres.statusCode()).isEqualTo(200); + } + @Test void get_hello_returningListOfBeans() { diff --git a/client/src/test/java/io/avaje/http/client/UrlBuilderTest.java b/client/src/test/java/io/avaje/http/client/UrlBuilderTest.java index 684a7e0..2c44105 100644 --- a/client/src/test/java/io/avaje/http/client/UrlBuilderTest.java +++ b/client/src/test/java/io/avaje/http/client/UrlBuilderTest.java @@ -6,6 +6,12 @@ class UrlBuilderTest { + @Test + void url() { + String url = new UrlBuilder("https://foo").url("http://bar").path("bazz").build(); + assertThat(url).isEqualTo("http://bar/bazz"); + } + @Test void path() { assertThat(new UrlBuilder("https://foo").path("bar").build()).isEqualTo("https://foo/bar"); From 4112bae96e5cc51cb49d11c9402f568b3dfcea40 Mon Sep 17 00:00:00 2001 From: rbygrave Date: Thu, 22 Apr 2021 13:56:33 +1200 Subject: [PATCH 2/2] API - Refactor rename ResponseListener to RequestListener #6 --- .../io/avaje/http/client/DHttpClientContext.java | 14 +++++++------- .../http/client/DHttpClientContextBuilder.java | 8 ++++---- .../io/avaje/http/client/DHttpClientRequest.java | 4 ++-- .../io/avaje/http/client/HttpClientContext.java | 4 ++-- ...{ResponseListener.java => RequestListener.java} | 6 +++--- .../java/io/avaje/http/client/RequestLogger.java | 2 +- .../java/io/avaje/http/client/BaseWebTest.java | 2 +- 7 files changed, 20 insertions(+), 20 deletions(-) rename client/src/main/java/io/avaje/http/client/{ResponseListener.java => RequestListener.java} (91%) diff --git a/client/src/main/java/io/avaje/http/client/DHttpClientContext.java b/client/src/main/java/io/avaje/http/client/DHttpClientContext.java index cfc10bf..fe51524 100644 --- a/client/src/main/java/io/avaje/http/client/DHttpClientContext.java +++ b/client/src/main/java/io/avaje/http/client/DHttpClientContext.java @@ -15,14 +15,14 @@ class DHttpClientContext implements HttpClientContext { private final String baseUrl; private final Duration requestTimeout; private final BodyAdapter bodyAdapter; - private final ResponseListener responseListener; + private final RequestListener requestListener; - DHttpClientContext(HttpClient httpClient, String baseUrl, Duration requestTimeout, BodyAdapter bodyAdapter, ResponseListener responseListener) { + DHttpClientContext(HttpClient httpClient, String baseUrl, Duration requestTimeout, BodyAdapter bodyAdapter, RequestListener requestListener) { this.httpClient = httpClient; this.baseUrl = baseUrl; this.requestTimeout = requestTimeout; this.bodyAdapter = bodyAdapter; - this.responseListener = responseListener; + this.requestListener = requestListener; } @Override @@ -128,14 +128,14 @@ List readList(Class cls, BodyContent content) { void afterResponse(DHttpClientRequest request) { - if (responseListener != null) { - responseListener.response(request.listenerEvent()); + if (requestListener != null) { + requestListener.response(request.listenerEvent()); } } void afterResponseHandler(DHttpClientRequest request) { - if (responseListener != null) { - responseListener.response(request.listenerEvent()); + if (requestListener != null) { + requestListener.response(request.listenerEvent()); } } } diff --git a/client/src/main/java/io/avaje/http/client/DHttpClientContextBuilder.java b/client/src/main/java/io/avaje/http/client/DHttpClientContextBuilder.java index efaf6f2..cb8227a 100644 --- a/client/src/main/java/io/avaje/http/client/DHttpClientContextBuilder.java +++ b/client/src/main/java/io/avaje/http/client/DHttpClientContextBuilder.java @@ -18,7 +18,7 @@ class DHttpClientContextBuilder implements HttpClientContext.Builder { private BodyAdapter bodyAdapter; - private ResponseListener responseListener; + private RequestListener requestListener; private CookieHandler cookieHandler = new CookieManager(); @@ -55,8 +55,8 @@ public HttpClientContext.Builder withBodyAdapter(BodyAdapter adapter) { } @Override - public HttpClientContext.Builder withResponseListener(ResponseListener responseListener) { - this.responseListener = responseListener; + public HttpClientContext.Builder withRequestListener(RequestListener requestListener) { + this.requestListener = requestListener; return this; } @@ -89,7 +89,7 @@ public HttpClientContext build() { if (client == null) { client = defaultClient(); } - return new DHttpClientContext(client, baseUrl, requestTimeout, bodyAdapter, responseListener); + return new DHttpClientContext(client, baseUrl, requestTimeout, bodyAdapter, requestListener); } private HttpClient defaultClient() { diff --git a/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java b/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java index 4d60d4b..a530078 100644 --- a/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java +++ b/client/src/main/java/io/avaje/http/client/DHttpClientRequest.java @@ -354,11 +354,11 @@ protected HttpRequest.Builder newRequest(String method, String url, HttpRequest. .method(method, body); } - ResponseListener.Event listenerEvent() { + RequestListener.Event listenerEvent() { return new ListenerEvent(); } - private class ListenerEvent implements ResponseListener.Event { + private class ListenerEvent implements RequestListener.Event { @Override public long responseTimeNanos() { diff --git a/client/src/main/java/io/avaje/http/client/HttpClientContext.java b/client/src/main/java/io/avaje/http/client/HttpClientContext.java index 6d16128..3759ebf 100644 --- a/client/src/main/java/io/avaje/http/client/HttpClientContext.java +++ b/client/src/main/java/io/avaje/http/client/HttpClientContext.java @@ -151,11 +151,11 @@ interface Builder { Builder withBodyAdapter(BodyAdapter adapter); /** - * Add a response listener. Note that {@link RequestLogger} is an + * Add a request listener. Note that {@link RequestLogger} is an * implementation for debug logging request/response headers and * content. */ - Builder withResponseListener(ResponseListener requestListener); + Builder withRequestListener(RequestListener requestListener); /** * Specify a cookie handler to use on the HttpClient. This would override the default cookie handler. diff --git a/client/src/main/java/io/avaje/http/client/ResponseListener.java b/client/src/main/java/io/avaje/http/client/RequestListener.java similarity index 91% rename from client/src/main/java/io/avaje/http/client/ResponseListener.java rename to client/src/main/java/io/avaje/http/client/RequestListener.java index b52327e..1a8c469 100644 --- a/client/src/main/java/io/avaje/http/client/ResponseListener.java +++ b/client/src/main/java/io/avaje/http/client/RequestListener.java @@ -7,10 +7,10 @@ /** * Listen to responses. *

- * {@link RequestLogger} is an implementation for debug logging the - * requests and responses. + * {@link RequestLogger} is an implementation for debug logging + * the requests and responses. */ -public interface ResponseListener { +public interface RequestListener { /** * Handle the response. diff --git a/client/src/main/java/io/avaje/http/client/RequestLogger.java b/client/src/main/java/io/avaje/http/client/RequestLogger.java index 05c8d31..d6c2934 100644 --- a/client/src/main/java/io/avaje/http/client/RequestLogger.java +++ b/client/src/main/java/io/avaje/http/client/RequestLogger.java @@ -13,7 +13,7 @@ /** * Logs request and response details for debug logging purposes. */ -public class RequestLogger implements ResponseListener { +public class RequestLogger implements RequestListener { private static final Logger log = LoggerFactory.getLogger(RequestLogger.class); diff --git a/client/src/test/java/io/avaje/http/client/BaseWebTest.java b/client/src/test/java/io/avaje/http/client/BaseWebTest.java index 7e95e9c..c2a8eb0 100644 --- a/client/src/test/java/io/avaje/http/client/BaseWebTest.java +++ b/client/src/test/java/io/avaje/http/client/BaseWebTest.java @@ -26,7 +26,7 @@ public static void shutdown() { public static HttpClientContext client() { return HttpClientContext.newBuilder() .withBaseUrl(baseUrl) - .withResponseListener(new RequestLogger()) + .withRequestListener(new RequestLogger()) .withBodyAdapter(new JacksonBodyAdapter(new ObjectMapper())) // .withBodyAdapter(new GsonBodyAdapter(new Gson())) .build();