{@code + * + * HttpClient client = HttpClient.builder() + * .baseUrl("http://localhost:8080") + * .bodyAdapter(new JacksonBodyAdapter()) + * .build(); + * + * HelloDto dto = client.request() + * .path("hello") + * .queryParam("name", "Rob") + * .queryParam("say", "Whats up") + * .GET() + * .bean(HelloDto.class); + * + * }+ */ +public interface HttpClient { + + /** + * Return the builder to config and build the client context. + * + *
{@code + * + * HttpClient client = HttpClient.builder() + * .baseUrl("http://localhost:8080") + * .bodyAdapter(new JacksonBodyAdapter()) + * .build(); + * + * HttpResponse+ */ + static Builder builder() { + return new DHttpClientBuilder(); + } + + /** + * Return the http client API implementation. + * + * @param clientInterface Ares = client.request() + * .path("hello") + * .GET().asString(); + * + * }
@Client
interface with annotated API methods.
+ * @param + * 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. + */ + @Deprecated + default BodyAdapter converters() { + return bodyAdapter(); + } + + /** + * Return the BodyAdapter that this client is using. + */ + BodyAdapter bodyAdapter(); + + /** + * Return the current aggregate metrics. + *
+ * These metrics are collected for all requests sent via this context. + */ + HttpClient.Metrics metrics(); + + /** + * Return the current metrics with the option of resetting the underlying counters. + *
+ * These metrics are collected for all requests sent via this context. + */ + HttpClient.Metrics metrics(boolean reset); + + /** + * Builds the HttpClient. + * + *
{@code + * + * HttpClient client = HttpClient.builder() + * .baseUrl("http://localhost:8080") + * .bodyAdapter(new JacksonBodyAdapter()) + * .build(); + * + * HelloDto dto = client.request() + * .path("hello") + * .queryParam("name", "Rob") + * .queryParam("say", "Whats up") + * .GET() + * .bean(HelloDto.class); + * + * }+ */ + 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 baseUrl(String baseUrl); + + /** + * Set the connection timeout to use. + * + * @see java.net.http.HttpClient.Builder#connectTimeout(Duration) + */ + Builder connectionTimeout(Duration connectionTimeout); + + /** + * Set the default request timeout. + * + * @see java.net.http.HttpRequest.Builder#timeout(Duration) + */ + Builder requestTimeout(Duration requestTimeout); + + /** + * Set the body adapter to use to convert beans to body content + * and response content back to beans. + */ + Builder bodyAdapter(BodyAdapter adapter); + + /** + * Set a RetryHandler to use to retry requests. + */ + Builder retryHandler(RetryHandler retryHandler); + + /** + * Disable or enable built in request and response logging. + *
+ * By default request logging is enabled. Set this to false to stop + * the default {@link RequestLogger} being registered to log request + * and response headers and bodies etc. + *
+ * With logging level set to {@code DEBUG} for + * {@code io.avaje.http.client.RequestLogger} the request and response + * are logged as a summary with response status and time. + *
+ * Set the logging level to {@code TRACE} to include the request + * and response headers and body payloads with truncation for large + * bodies. + * + *
+ * We can also use {@link HttpClientRequest#suppressLogging()} to suppress + * logging on specific requests. + *
+ * Logging of Authorization headers is suppressed. + * {@link AuthTokenProvider} requests are suppressed. + * + * @param requestLogging Disable/enable the registration of the default logger + * @see RequestLogger + */ + Builder requestLogging(boolean requestLogging); + + /** + * Add a request listener. Multiple listeners may be added, when + * do so they will process events in the order they were added. + *
+ * Note that {@link RequestLogger} is an implementation for debug + * logging request/response headers and content which is registered + * by default depending on {@link #requestLogging(boolean)}. + * + * @see RequestLogger + */ + Builder requestListener(RequestListener requestListener); + + /** + * Add a request interceptor. Multiple interceptors may be added. + */ + Builder requestIntercept(RequestIntercept requestIntercept); + + /** + * Add a Authorization token provider. + *
+ * When set all requests are expected to use a Authorization Bearer token + * unless they are marked via {@link HttpClientRequest#skipAuthToken()}. + *
+ * The AuthTokenProvider obtains a new token typically with an expiry. This + * is automatically called as needed and the Authorization Bearer header set + * on all requests (not marked with skipAuthToken()). + */ + Builder authTokenProvider(AuthTokenProvider authTokenProvider); + + /** + * Set the underlying HttpClient to use. + *
+ * Used when we wish to control all options of the HttpClient. + */ + Builder client(java.net.http.HttpClient client); + + /** + * Specify a cookie handler to use on the HttpClient. This would override the default cookie handler. + * + * @see java.net.http.HttpClient.Builder#cookieHandler(CookieHandler) + */ + Builder cookieHandler(CookieHandler cookieHandler); + + /** + * Specify the redirect policy. Defaults to HttpClient.Redirect.NORMAL. + * + * @see java.net.http.HttpClient.Builder#followRedirects(java.net.http.HttpClient.Redirect) + */ + Builder redirect(java.net.http.HttpClient.Redirect redirect); + + /** + * Specify the HTTP version. Defaults to not set. + * + * @see java.net.http.HttpClient.Builder#version(java.net.http.HttpClient.Version) + */ + Builder version(java.net.http.HttpClient.Version version); + + /** + * Specify the Executor to use for asynchronous tasks. + * If not specified a default executor will be used. + * + * @see java.net.http.HttpClient.Builder#executor(Executor) + */ + Builder executor(Executor executor); + + /** + * Set the proxy to the underlying {@link java.net.http.HttpClient}. + * + * @see java.net.http.HttpClient.Builder#proxy(ProxySelector) + */ + Builder proxy(ProxySelector proxySelector); + + /** + * Set the sslContext to the underlying {@link java.net.http.HttpClient}. + * + * @see java.net.http.HttpClient.Builder#sslContext(SSLContext) + */ + Builder sslContext(SSLContext sslContext); + + /** + * Set the sslParameters to the underlying {@link java.net.http.HttpClient}. + * + * @see java.net.http.HttpClient.Builder#sslParameters(SSLParameters) + */ + Builder sslParameters(SSLParameters sslParameters); + + /** + * Set a HttpClient authenticator to the underlying {@link java.net.http.HttpClient}. + * + * @see java.net.http.HttpClient.Builder#authenticator(Authenticator) + */ + Builder authenticator(Authenticator authenticator); + + /** + * Set the priority for HTTP/2 requests to the underlying {@link java.net.http.HttpClient}. + * + * @see java.net.http.HttpClient.Builder#priority(int) + */ + Builder priority(int priority); + + /** + * Configure BodyAdapter and RetryHandler using dependency injection BeanScope. + */ + Builder configureWith(BeanScope beanScope); + + /** + * Return the state of the builder. + */ + Builder.State state(); + + /** + * Build and return the context. + * + *
{@code + * + * HttpClient client = HttpClient.builder() + * .baseUrl("http://localhost:8080") + * .bodyAdapter(new JacksonBodyAdapter()) + * .build(); + * + * HelloDto dto = client.request() + * .path("hello") + * .queryParam("say", "Whats up") + * .GET() + * .bean(HelloDto.class); + * + * }+ */ + HttpClient build(); + + /** + * The state of the builder with methods to read the set state. + */ + interface State { + + /** + * Return the base URL. + */ + String baseUrl(); + + /** + * Return the body adapter. + */ + BodyAdapter bodyAdapter(); + + /** + * Return the HttpClient. + */ + java.net.http.HttpClient client(); + + /** + * Return true if requestLogging is on. + */ + boolean requestLogging(); + + /** + * Return the request timeout. + */ + Duration requestTimeout(); + + /** + * Return the retry handler. + */ + RetryHandler retryHandler(); + } + } + + /** + * Statistic metrics collected to provide an overview of activity of this client. + */ + interface Metrics extends HttpClientContext.Metrics { + + } +} 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 d65bf01..12ce33d 100644 --- a/client/src/main/java/io/avaje/http/client/HttpClientContext.java +++ b/client/src/main/java/io/avaje/http/client/HttpClientContext.java @@ -1,21 +1,25 @@ package io.avaje.http.client; +import io.avaje.inject.BeanScope; + import javax.net.ssl.SSLContext; import javax.net.ssl.SSLParameters; import java.net.Authenticator; import java.net.CookieHandler; import java.net.ProxySelector; import java.net.http.HttpClient; -import java.net.http.HttpResponse; import java.time.Duration; import java.util.concurrent.Executor; /** + * Deprecated in favor of {@link io.avaje.http.client.HttpClient}. + * Migrate to using {@link io.avaje.http.client.HttpClient#builder()}. + *
* The HTTP client context that we use to build and process requests. * *
{@code * - * HttpClientContext ctx = HttpClientContext.newBuilder() + * HttpClientContext ctx = HttpClientContext.builder() * .baseUrl("http://localhost:8080") * .bodyAdapter(new JacksonBodyAdapter()) * .build(); @@ -29,14 +33,17 @@ * * }*/ -public interface HttpClientContext { +@Deprecated +public interface HttpClientContext extends io.avaje.http.client.HttpClient { /** + * Deprecated - migrate to {@link io.avaje.http.client.HttpClient#builder()}. + *
* Return the builder to config and build the client context. * *
{@code * - * HttpClientContext ctx = HttpClientContext.newBuilder() + * HttpClientContext ctx = HttpClientContext.builder() * .baseUrl("http://localhost:8080") * .bodyAdapter(new JacksonBodyAdapter()) * .build(); @@ -47,94 +54,25 @@ public interface HttpClientContext { * * }*/ - static HttpClientContext.Builder newBuilder() { + @Deprecated + static HttpClientContext.Builder builder() { return new DHttpClientContextBuilder(); } /** - * Return the http client API implementation. - * - * @param clientInterface A
@Client
interface with annotated API methods.
- * @param - * This is the body adapter used to convert request and response - * bodies to java types. For example using Jackson with JSON payloads. - */ - BodyAdapter converters(); - - /** - * Return the underlying http client. - */ - HttpClient httpClient(); - - /** - * Return the current aggregate metrics. - *
- * These metrics are collected for all requests sent via this context. - */ - Metrics metrics(); - - /** - * Return the current metrics with the option of resetting the underlying counters. - *
- * These metrics are collected for all requests sent via this context.
- */
- Metrics metrics(boolean reset);
-
- /**
- * Check the response status code and throw HttpException if the status
- * code is in the error range.
- */
- void checkResponse(HttpResponse> response);
-
- /**
- * Return the response content taking into account content encoding.
- *
- * @param httpResponse The HTTP response to decode the content from
- * @return The decoded content
- */
- BodyContent readContent(HttpResponse
@@ -106,6 +108,51 @@ public interface HttpClientResponse {
*/
+ * If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
+ * the HttpResponse. This is the cause in the CompletionException.
+ *
+ * @param type The parameterized type of the bean to convert the response content into.
+ * @return The bean the response is converted into.
+ * @throws HttpException when the response has error status codes
+ */
+
+ * If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
+ * the HttpResponse. This is the cause in the CompletionException.
+ *
+ * @param type The parameterized type of the bean to convert the response content into.
+ * @return The list of beans the response is converted into.
+ * @throws HttpException when the response has error status codes
+ */
+
+ * Typically the response is expected to be {@literal application/x-json-stream}
+ * newline delimited json payload.
+ *
+ * Note that for this stream request the response content is not deemed
+ * 'loggable' by avaje-http-client. This is because the entire response
+ * may not be available at the time of the callback. As such {@link RequestLogger}
+ * will not include response content when logging stream request/response
+ *
+ * If the HTTP statusCode is not in the 2XX range a HttpException is throw which contains
+ * the HttpResponse. This is the cause in the CompletionException.
+ *
+ * @param type The parameterized type of the bean to convert the response content into.
+ * @return The stream of beans from the response
+ * @throws HttpException when the response has error status codes
+ */
+
@@ -167,6 +214,14 @@ public interface HttpClientResponse {
/**
* Return the response using the given response body handler.
*/
-
- * This implementation logs the request and response with the same
- * single logging entry rather than separate logging of the request
- * and response.
+ * This implementation logs the request and response with the same single logging entry
+ * rather than separate logging of the request and response.
*
- * With logging level set to {@code DEBUG} for
- * {@code io.avaje.http.client.RequestLogger} the request and response
- * are logged as a summary with response status and time.
+ * With logging level set to {@code DEBUG} for {@code io.avaje.http.client.RequestLogger} the
+ * request and response are logged as a summary with response status and time.
*
- * Set the logging level to {@code TRACE} to include the request
- * and response headers and body payloads with truncation for large
- * bodies.
+ * Set the logging level to {@code TRACE} to include the request and response headers and body
+ * payloads with truncation for large bodies.
+ *
+ * Using System.Logger, messages by default go to JUL (Java Util Logging) unless a provider
+ * is registered. We can use Content-Encoding
http header.
- *
- * @param httpResponse The HTTP response
- * @return The decoded content
- */
- byte[] decodeContent(HttpResponse{@code
*
- * HttpClientContext ctx = HttpClientContext.newBuilder()
+ * HttpClientContext ctx = HttpClientContext.builder()
* .baseUrl("http://localhost:8080")
* .bodyAdapter(new JacksonBodyAdapter())
* .build();
@@ -164,6 +102,13 @@ interface Builder {
*/
Builder baseUrl(String baseUrl);
+ /**
+ * Set the connection timeout to use.
+ *
+ * @see java.net.http.HttpClient.Builder#connectTimeout(Duration)
+ */
+ Builder connectionTimeout(Duration connectionTimeout);
+
/**
* Set the default request timeout.
*
@@ -303,12 +248,22 @@ interface Builder {
*/
Builder priority(int priority);
+ /**
+ * Configure BodyAdapter and RetryHandler using dependency injection BeanScope.
+ */
+ Builder configureWith(BeanScope beanScope);
+
+ /**
+ * Return the state of the builder.
+ */
+ State state();
+
/**
* Build and return the context.
*
*
{@code
*
- * HttpClientContext ctx = HttpClientContext.newBuilder()
+ * HttpClientContext ctx = HttpClientContext.builder()
* .baseUrl("http://localhost:8080")
* .bodyAdapter(new JacksonBodyAdapter())
* .build();
@@ -322,13 +277,19 @@ interface Builder {
* }
*/
HttpClientContext build();
+
+ /**
+ * The state of the builder with methods to read the set state.
+ */
+ interface State extends io.avaje.http.client.HttpClient.Builder.State {
+
+ }
}
/**
* Statistic metrics collected to provide an overview of activity of this client.
*/
interface Metrics {
-
/**
* Return the total number of responses.
*/
diff --git a/client/src/main/java/io/avaje/http/client/HttpClientResponse.java b/client/src/main/java/io/avaje/http/client/HttpClientResponse.java
index 088f9a2..13f4e65 100644
--- a/client/src/main/java/io/avaje/http/client/HttpClientResponse.java
+++ b/client/src/main/java/io/avaje/http/client/HttpClientResponse.java
@@ -1,6 +1,7 @@
package io.avaje.http.client;
import java.io.InputStream;
+import java.lang.reflect.ParameterizedType;
import java.net.http.HttpResponse;
import java.nio.file.Path;
import java.util.List;
@@ -85,6 +86,7 @@ public interface HttpClientResponse {
*/
{@code
*
- * HttpClientContext.newBuilder()
+ * HttpClientContext.builder()
* .baseUrl(baseUrl)
* .bodyAdapter(new JacksonBodyAdapter())
* .build();
diff --git a/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java b/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java
index 2ae8e8a..610c877 100644
--- a/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java
+++ b/client/src/main/java/io/avaje/http/client/JsonbBodyAdapter.java
@@ -1,17 +1,19 @@
package io.avaje.http.client;
-import io.avaje.jsonb.JsonType;
-import io.avaje.jsonb.Jsonb;
-
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
+import io.avaje.jsonb.JsonType;
+import io.avaje.jsonb.Jsonb;
+
/**
- * avaje jsonb BodyAdapter to read and write beans as JSON.
+ * Avaje Jsonb BodyAdapter to read and write beans as JSON.
*
*
{@code
*
- * HttpClientContext.newBuilder()
+ * HttpClientContext.builder()
* .baseUrl(baseUrl)
* .bodyAdapter(new JsonbBodyAdapter())
* .build();
@@ -21,9 +23,9 @@
public final class JsonbBodyAdapter implements BodyAdapter {
private final Jsonb jsonb;
- private final ConcurrentHashMap
> listReader(ParameterizedType cls) {
+ return (BodyReader
>) listReaderCache.computeIfAbsent(cls, aClass -> new JReader<>(jsonb.type(cls).list()));
+ }
+
@SuppressWarnings("unchecked")
@Override
public
> listReader(Class
System.Logger
.
*