From 00ebd64752c182ab0a584fb17d6826b676e1cc0e Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Wed, 5 Feb 2025 11:04:32 +0100 Subject: [PATCH] Async client can now be used internally by the Fluent facade --- .../hc/client5/http/fluent/Executor.java | 89 +++++++++++++++---- .../hc/client5/http/fluent/Request.java | 16 +++- 2 files changed, 89 insertions(+), 16 deletions(-) diff --git a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Executor.java b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Executor.java index 5e52e26323..7527ae7070 100644 --- a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Executor.java +++ b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Executor.java @@ -28,6 +28,7 @@ import java.io.IOException; import java.net.URISyntaxException; +import java.util.concurrent.locks.ReentrantLock; import org.apache.hc.client5.http.auth.AuthCache; import org.apache.hc.client5.http.auth.AuthScope; @@ -36,15 +37,21 @@ import org.apache.hc.client5.http.auth.UsernamePasswordCredentials; import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; +import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; import org.apache.hc.client5.http.impl.auth.BasicAuthCache; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.auth.BasicScheme; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.HttpClientBuilder; +import org.apache.hc.client5.http.impl.compat.ClassicToAsyncAdaptor; import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManagerBuilder; +import org.apache.hc.client5.http.impl.nio.PoolingAsyncClientConnectionManagerBuilder; import org.apache.hc.client5.http.protocol.HttpClientContext; +import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; /** * Executor for {@link Request}s. @@ -56,30 +63,82 @@ */ public class Executor { - final static CloseableHttpClient CLIENT; + private static final ReentrantLock LOCK = new ReentrantLock(); + private static volatile CloseableHttpClient CLIENT; + private static volatile CloseableHttpClient ASYNC_CLIENT; - static { - CLIENT = HttpClientBuilder.create() - .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create() + static CloseableHttpClient GET_CLASSIC_CLIENT() { + final CloseableHttpClient client = CLIENT; + if (client != null) { + return client; + } + LOCK.lock(); + try { + if (CLIENT == null) { + CLIENT = HttpClientBuilder.create() + .setConnectionManager(PoolingHttpClientConnectionManagerBuilder.create() + .useSystemProperties() + .setMaxConnPerRoute(100) + .setMaxConnTotal(200) + .setDefaultConnectionConfig(ConnectionConfig.custom() + .setValidateAfterInactivity(TimeValue.ofSeconds(10)) + .build()) + .build()) .useSystemProperties() - .setMaxConnPerRoute(100) - .setMaxConnTotal(200) - .setDefaultConnectionConfig(ConnectionConfig.custom() - .setValidateAfterInactivity(TimeValue.ofSeconds(10)) + .evictExpiredConnections() + .evictIdleConnections(TimeValue.ofMinutes(1)) + .build(); + } + return CLIENT; + } finally { + LOCK.unlock(); + } + } + + static CloseableHttpClient GET_ASYNC_CLIENT() { + final CloseableHttpClient client = ASYNC_CLIENT; + if (client != null) { + return client; + } + LOCK.lock(); + try { + if (ASYNC_CLIENT == null) { + ASYNC_CLIENT = new ClassicToAsyncAdaptor(HttpAsyncClientBuilder.create() + .setConnectionManager(PoolingAsyncClientConnectionManagerBuilder.create() + .useSystemProperties() + .setMaxConnPerRoute(100) + .setMaxConnTotal(200) + .setMessageMultiplexing(true) + .setDefaultConnectionConfig(ConnectionConfig.custom() + .setValidateAfterInactivity(TimeValue.ofSeconds(10)) + .build()) .build()) - .build()) - .useSystemProperties() - .evictExpiredConnections() - .evictIdleConnections(TimeValue.ofMinutes(1)) - .build(); + .useSystemProperties() + .evictExpiredConnections() + .evictIdleConnections(TimeValue.ofMinutes(1)) + .build(), Timeout.ofMinutes(5)); + } + return ASYNC_CLIENT; + } finally { + LOCK.unlock(); + } } public static Executor newInstance() { - return new Executor(CLIENT); + return new Executor(GET_CLASSIC_CLIENT()); } public static Executor newInstance(final CloseableHttpClient httpclient) { - return new Executor(httpclient != null ? httpclient : CLIENT); + return new Executor(httpclient != null ? httpclient : GET_CLASSIC_CLIENT()); + } + + /** + * This feature is considered experimental and may be discontinued in the future. + * @since 5.5 + */ + @Experimental + public static Executor newInstance(final CloseableHttpAsyncClient httpclient) { + return new Executor(httpclient != null ? new ClassicToAsyncAdaptor(httpclient, Timeout.ofMinutes(5)) : GET_ASYNC_CLIENT()); } private final CloseableHttpClient httpclient; diff --git a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java index 13172fc50c..afc3900397 100644 --- a/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java +++ b/httpclient5-fluent/src/main/java/org/apache/hc/client5/http/fluent/Request.java @@ -47,6 +47,7 @@ import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.protocol.HttpClientContext; import org.apache.hc.client5.http.utils.DateUtils; +import org.apache.hc.core5.annotation.Experimental; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.ContentType; @@ -202,7 +203,20 @@ ClassicHttpResponse internalExecute( } public Response execute() throws IOException { - return execute(Executor.CLIENT); + return execute(Executor.GET_CLASSIC_CLIENT()); + } + + /** + * Execute the request using an HTTP/2 capable engine. The exact protocol version + * will still have to be negotiated by individual connections. + *

+ * This feature is considered experimental and may be discontinued in the future. + * + * @since 5.5 + */ + @Experimental + public Response executeHttp2() throws IOException { + return execute(Executor.GET_ASYNC_CLIENT()); } public Response execute(final CloseableHttpClient client) throws IOException {