From f36637dc2f2905e52df76b3e95e71a9732a06982 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Tue, 11 May 2021 21:26:11 +0200 Subject: [PATCH] Moved connection management related settings from RequestConfig to new class ConnectionMgmtConfig --- .../AbstractHttp1IntegrationTestBase.java | 6 +- .../hc/client5/testing/async/TestH2Async.java | 4 + .../testing/async/TestH2AsyncRedirect.java | 4 + .../async/TestH2ClientAuthentication.java | 4 + .../client5/testing/async/TestH2Reactive.java | 4 + .../client5/testing/async/TestHttp1Async.java | 6 +- .../async/TestHttp1AsyncRedirects.java | 6 +- .../TestHttp1AsyncStatefulConnManagement.java | 6 +- .../async/TestHttp1ClientAuthentication.java | 6 +- .../testing/async/TestHttp1Reactive.java | 6 +- .../testing/sync/LocalServerTestBase.java | 6 +- .../client5/http/config/ConnectionConfig.java | 205 ++++++++++++++++++ .../hc/client5/http/config/RequestConfig.java | 23 +- .../http/impl/async/H2AsyncClientBuilder.java | 30 ++- .../impl/async/InternalH2AsyncClient.java | 5 +- .../async/InternalH2AsyncExecRuntime.java | 9 +- .../http/impl/async/InternalH2ConnPool.java | 102 +++++++++ .../http/impl/async/MinimalH2AsyncClient.java | 16 +- .../io/BasicHttpClientConnectionManager.java | 29 ++- .../PoolingHttpClientConnectionManager.java | 105 +++++++-- ...ingHttpClientConnectionManagerBuilder.java | 61 ++++-- .../PoolingAsyncClientConnectionManager.java | 82 +++++-- ...ngAsyncClientConnectionManagerBuilder.java | 40 +++- .../http/config/TestRequestConfig.java | 6 - .../http/examples/ClientConfiguration.java | 23 +- 25 files changed, 685 insertions(+), 109 deletions(-) create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/config/ConnectionConfig.java create mode 100644 httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2ConnPool.java diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java index 2a7a51d85..964e1d329 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/AbstractHttp1IntegrationTestBase.java @@ -30,6 +30,7 @@ package org.apache.hc.client5.testing.async; import java.net.InetSocketAddress; import java.util.concurrent.Future; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.HttpAsyncClientBuilder; @@ -69,6 +70,10 @@ public abstract class AbstractHttp1IntegrationTestBase extends AbstractServerTes @Override protected void before() throws Throwable { connManager = PoolingAsyncClientConnectionManagerBuilder.create() + .setDefaultConnectionConfig(ConnectionConfig.custom() + .setConnectTimeout(TIMEOUT) + .setSocketTimeout(TIMEOUT) + .build()) .setTlsStrategy(new DefaultClientTlsStrategy(SSLTestContexts.createClientSSLContext())) .build(); } @@ -91,7 +96,6 @@ public abstract class AbstractHttp1IntegrationTestBase extends AbstractServerTes clientBuilder = HttpAsyncClientBuilder.create() .setDefaultRequestConfig(RequestConfig.custom() .setConnectionRequestTimeout(TIMEOUT) - .setConnectTimeout(TIMEOUT) .build()) .setConnectionManager(connManager); } diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2Async.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2Async.java index 207614392..a6795d179 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2Async.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/async/TestH2Async.java @@ -29,6 +29,7 @@ package org.apache.hc.client5.testing.async; import java.util.Arrays; import java.util.Collection; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.async.CloseableHttpAsyncClient; import org.apache.hc.client5.http.impl.async.H2AsyncClientBuilder; @@ -63,7 +64,10 @@ public class TestH2Async extends AbstractHttpAsyncFundamentalsTest. + * + */ + +package org.apache.hc.client5.http.config; + +import java.util.concurrent.TimeUnit; + +import org.apache.hc.core5.annotation.Contract; +import org.apache.hc.core5.annotation.ThreadingBehavior; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; + +/** + * Immutable class encapsulating connection initialization and management settings. + * + * @since 5.2 + */ +@Contract(threading = ThreadingBehavior.IMMUTABLE) +public class ConnectionConfig implements Cloneable { + + private static final Timeout DEFAULT_CONNECT_TIMEOUT = Timeout.ofMinutes(3); + + public static final ConnectionConfig DEFAULT = new Builder().build(); + + private final Timeout connectTimeout; + private final Timeout socketTimeout; + private final TimeValue validateAfterInactivity; + + /** + * Intended for CDI compatibility + */ + protected ConnectionConfig() { + this(DEFAULT_CONNECT_TIMEOUT, null, null); + } + + ConnectionConfig( + final Timeout connectTimeout, + final Timeout socketTimeout, + final TimeValue validateAfterInactivity) { + super(); + this.connectTimeout = connectTimeout; + this.socketTimeout = socketTimeout; + this.validateAfterInactivity = validateAfterInactivity; + } + + /** + * @see Builder#setSocketTimeout(Timeout) + */ + public Timeout getSocketTimeout() { + return socketTimeout; + } + + /** + * @see Builder#setConnectTimeout(Timeout) + */ + public Timeout getConnectTimeout() { + return connectTimeout; + } + + /** + * @see Builder#setValidateAfterInactivity(TimeValue) + */ + public TimeValue getValidateAfterInactivity() { + return validateAfterInactivity; + } + + @Override + protected ConnectionConfig clone() throws CloneNotSupportedException { + return (ConnectionConfig) super.clone(); + } + + @Override + public String toString() { + final StringBuilder builder = new StringBuilder(); + builder.append("["); + builder.append(", connectTimeout=").append(connectTimeout); + builder.append(", socketTimeout=").append(socketTimeout); + builder.append(", validateAfterInactivity=").append(validateAfterInactivity); + builder.append("]"); + return builder.toString(); + } + + public static ConnectionConfig.Builder custom() { + return new Builder(); + } + + public static ConnectionConfig.Builder copy(final ConnectionConfig config) { + return new Builder() + .setConnectTimeout(config.getConnectTimeout()) + .setSocketTimeout(config.getSocketTimeout()) + .setValidateAfterInactivity(config.getValidateAfterInactivity()); + } + + public static class Builder { + + private Timeout socketTimeout; + private Timeout connectTimeout; + private TimeValue validateAfterInactivity; + + Builder() { + super(); + this.connectTimeout = DEFAULT_CONNECT_TIMEOUT; + } + + + /** + * @see #setSocketTimeout(Timeout) + */ + public Builder setSocketTimeout(final int soTimeout, final TimeUnit timeUnit) { + this.socketTimeout = Timeout.of(soTimeout, timeUnit); + return this; + } + + /** + * Determines the default socket timeout value for I/O operations. + *

+ * Default: {@code null} + *

+ * + * @return the default socket timeout value for I/O operations. + */ + public Builder setSocketTimeout(final Timeout soTimeout) { + this.socketTimeout = soTimeout; + return this; + } + + /** + * Determines the timeout until a new connection is fully established. + * This may also include transport security negotiation exchanges + * such as {@code SSL} or {@code TLS} protocol negotiation). + *

+ * A timeout value of zero is interpreted as an infinite timeout. + *

+ *

+ * Default: 3 minutes + *

+ */ + public Builder setConnectTimeout(final Timeout connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + + /** + * @see #setConnectTimeout(Timeout) + */ + public Builder setConnectTimeout(final long connectTimeout, final TimeUnit timeUnit) { + this.connectTimeout = Timeout.of(connectTimeout, timeUnit); + return this; + } + + /** + * Defines period of inactivity after which persistent connections must + * be re-validated prior to being leased to the consumer. Negative values passed + * to this method disable connection validation. + *

+ * Default: {@code null} + *

+ */ + public Builder setValidateAfterInactivity(final TimeValue validateAfterInactivity) { + this.validateAfterInactivity = validateAfterInactivity; + return this; + } + + /** + * @see #setValidateAfterInactivity(TimeValue) + */ + public Builder setValidateAfterInactivity(final long validateAfterInactivity, final TimeUnit timeUnit) { + this.validateAfterInactivity = TimeValue.of(validateAfterInactivity, timeUnit); + return this; + } + + public ConnectionConfig build() { + return new ConnectionConfig( + connectTimeout != null ? connectTimeout : DEFAULT_CONNECT_TIMEOUT, + socketTimeout, + validateAfterInactivity); + } + + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java b/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java index 9bcf749cf..b3a78605a 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/config/RequestConfig.java @@ -43,7 +43,6 @@ import org.apache.hc.core5.util.Timeout; public class RequestConfig implements Cloneable { private static final Timeout DEFAULT_CONNECTION_REQUEST_TIMEOUT = Timeout.ofMinutes(3); - private static final Timeout DEFAULT_CONNECT_TIMEOUT = Timeout.ofMinutes(3); private static final TimeValue DEFAULT_CONN_KEEP_ALIVE = TimeValue.ofMinutes(3); public static final RequestConfig DEFAULT = new Builder().build(); @@ -69,7 +68,7 @@ public class RequestConfig implements Cloneable { */ protected RequestConfig() { this(false, null, null, false, false, 0, false, null, null, - DEFAULT_CONNECTION_REQUEST_TIMEOUT, DEFAULT_CONNECT_TIMEOUT, null, DEFAULT_CONN_KEEP_ALIVE, false, false); + DEFAULT_CONNECTION_REQUEST_TIMEOUT, null, null, DEFAULT_CONN_KEEP_ALIVE, false, false); } RequestConfig( @@ -115,7 +114,11 @@ public class RequestConfig implements Cloneable { /** * @see Builder#setProxy(HttpHost) + * + * @deprecated Use {@link org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner} + * or a custom {@link org.apache.hc.client5.http.routing.HttpRoutePlanner}. */ + @Deprecated public HttpHost getProxy() { return proxy; } @@ -178,7 +181,10 @@ public class RequestConfig implements Cloneable { /** * @see Builder#setConnectTimeout(Timeout) + * + * @deprecated Use {@link ConnectionConfig#getConnectTimeout()}. */ + @Deprecated public Timeout getConnectTimeout() { return connectTimeout; } @@ -286,7 +292,6 @@ public class RequestConfig implements Cloneable { this.maxRedirects = 50; this.authenticationEnabled = true; this.connectionRequestTimeout = DEFAULT_CONNECTION_REQUEST_TIMEOUT; - this.connectTimeout = DEFAULT_CONNECT_TIMEOUT; this.contentCompressionEnabled = true; this.hardCancellationEnabled = true; } @@ -323,7 +328,11 @@ public class RequestConfig implements Cloneable { *

* Default: {@code null} *

+ * + * @deprecated Use {@link org.apache.hc.client5.http.impl.routing.DefaultProxyRoutePlanner} + * or a custom {@link org.apache.hc.client5.http.routing.HttpRoutePlanner}. */ + @Deprecated public Builder setProxy(final HttpHost proxy) { this.proxy = proxy; return this; @@ -444,7 +453,10 @@ public class RequestConfig implements Cloneable { *

* Default: 3 minutes *

+ * + * @deprecated Use {@link ConnectionConfig.Builder#setConnectTimeout(Timeout)}. */ + @Deprecated public Builder setConnectTimeout(final Timeout connectTimeout) { this.connectTimeout = connectTimeout; return this; @@ -452,7 +464,10 @@ public class RequestConfig implements Cloneable { /** * @see #setConnectTimeout(Timeout) + * + * @deprecated Use {@link ConnectionConfig.Builder#setConnectTimeout(long, TimeUnit)}. */ + @Deprecated public Builder setConnectTimeout(final long connectTimeout, final TimeUnit timeUnit) { this.connectTimeout = Timeout.of(connectTimeout, timeUnit); return this; @@ -570,7 +585,7 @@ public class RequestConfig implements Cloneable { targetPreferredAuthSchemes, proxyPreferredAuthSchemes, connectionRequestTimeout != null ? connectionRequestTimeout : DEFAULT_CONNECTION_REQUEST_TIMEOUT, - connectTimeout != null ? connectTimeout : DEFAULT_CONNECT_TIMEOUT, + connectTimeout, responseTimeout, connectionKeepAlive != null ? connectionKeepAlive : DEFAULT_CONN_KEEP_ALIVE, contentCompressionEnabled, diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java index 6c2a0dc7a..494e925c3 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/H2AsyncClientBuilder.java @@ -45,6 +45,7 @@ import org.apache.hc.client5.http.async.AsyncExecChainHandler; import org.apache.hc.client5.http.auth.AuthSchemeFactory; import org.apache.hc.client5.http.auth.CredentialsProvider; import org.apache.hc.client5.http.auth.StandardAuthScheme; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.cookie.BasicCookieStore; import org.apache.hc.client5.http.cookie.CookieSpecFactory; @@ -74,7 +75,9 @@ import org.apache.hc.client5.http.routing.HttpRoutePlanner; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.concurrent.DefaultThreadFactory; +import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.Header; +import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpRequestInterceptor; import org.apache.hc.core5.http.HttpResponseInterceptor; import org.apache.hc.core5.http.config.CharCodingConfig; @@ -89,7 +92,6 @@ import org.apache.hc.core5.http.protocol.HttpProcessorBuilder; import org.apache.hc.core5.http.protocol.RequestTargetHost; import org.apache.hc.core5.http.protocol.RequestUserAgent; import org.apache.hc.core5.http2.config.H2Config; -import org.apache.hc.core5.http2.nio.pool.H2ConnPool; import org.apache.hc.core5.http2.protocol.H2RequestConnControl; import org.apache.hc.core5.http2.protocol.H2RequestContent; import org.apache.hc.core5.http2.protocol.H2RequestTargetHost; @@ -189,6 +191,7 @@ public class H2AsyncClientBuilder { private String userAgent; private Collection defaultHeaders; private RequestConfig defaultRequestConfig; + private Resolver connectionConfigResolver; private boolean evictIdleConnections; private TimeValue maxIdleTime; @@ -500,6 +503,26 @@ public class H2AsyncClientBuilder { return this; } + /** + * Assigns {@link Resolver} for {@link ConnectionConfig} on a per host basis. + * + * @since 5.2 + */ + public final H2AsyncClientBuilder setConnectionConfigResolver(final Resolver connectionConfigResolver) { + this.connectionConfigResolver = connectionConfigResolver; + return this; + } + + /** + * Assigns the same {@link ConnectionConfig} for all hosts. + * + * @since 5.2 + */ + public final H2AsyncClientBuilder setDefaultConnectionConfig(final ConnectionConfig connectionConfig) { + this.connectionConfigResolver = (host) -> connectionConfig; + return this; + } + /** * Use system properties when creating and configuring default * implementations. @@ -784,7 +807,8 @@ public class H2AsyncClientBuilder { } final MultihomeConnectionInitiator connectionInitiator = new MultihomeConnectionInitiator(ioReactor, dnsResolver); - final H2ConnPool connPool = new H2ConnPool(connectionInitiator, host -> null, tlsStrategyCopy); + final InternalH2ConnPool connPool = new InternalH2ConnPool(connectionInitiator, host -> null, tlsStrategyCopy); + connPool.setConnectionConfigResolver(connectionConfigResolver); List closeablesCopy = closeables != null ? new ArrayList<>(closeables) : null; if (closeablesCopy == null) { @@ -821,7 +845,7 @@ public class H2AsyncClientBuilder { private final Thread thread; - public IdleConnectionEvictor(final H2ConnPool connPool, final TimeValue maxIdleTime) { + public IdleConnectionEvictor(final InternalH2ConnPool connPool, final TimeValue maxIdleTime) { this.thread = new DefaultThreadFactory("idle-connection-evictor", true).newThread(() -> { try { while (!Thread.currentThread().isInterrupted()) { diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java index 6ebaf66fb..cb758f60f 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncClient.java @@ -47,7 +47,6 @@ import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.config.Lookup; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; -import org.apache.hc.core5.http2.nio.pool.H2ConnPool; import org.apache.hc.core5.reactor.DefaultConnectingIOReactor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -68,14 +67,14 @@ public final class InternalH2AsyncClient extends InternalAbstractHttpAsyncClient private static final Logger LOG = LoggerFactory.getLogger(InternalH2AsyncClient.class); private final HttpRoutePlanner routePlanner; - private final H2ConnPool connPool; + private final InternalH2ConnPool connPool; InternalH2AsyncClient( final DefaultConnectingIOReactor ioReactor, final AsyncExecChainElement execChain, final AsyncPushConsumerRegistry pushConsumerRegistry, final ThreadFactory threadFactory, - final H2ConnPool connPool, + final InternalH2ConnPool connPool, final HttpRoutePlanner routePlanner, final Lookup cookieSpecRegistry, final Lookup authSchemeRegistry, diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java index 957eeacf8..84411e22a 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2AsyncExecRuntime.java @@ -44,7 +44,6 @@ import org.apache.hc.core5.http.nio.AsyncClientExchangeHandler; import org.apache.hc.core5.http.nio.AsyncPushConsumer; import org.apache.hc.core5.http.nio.HandlerFactory; import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; -import org.apache.hc.core5.http2.nio.pool.H2ConnPool; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.IOSession; @@ -56,14 +55,14 @@ import org.slf4j.Logger; class InternalH2AsyncExecRuntime implements AsyncExecRuntime { private final Logger log; - private final H2ConnPool connPool; + private final InternalH2ConnPool connPool; private final HandlerFactory pushHandlerFactory; private final AtomicReference sessionRef; private volatile boolean reusable; InternalH2AsyncExecRuntime( final Logger log, - final H2ConnPool connPool, + final InternalH2ConnPool connPool, final HandlerFactory pushHandlerFactory) { super(); this.log = log; @@ -91,9 +90,7 @@ class InternalH2AsyncExecRuntime implements AsyncExecRuntime { if (log.isDebugEnabled()) { log.debug("{} acquiring endpoint ({})", id, connectTimeout); } - return Operations.cancellable(connPool.getSession( - target, - connectTimeout, + return Operations.cancellable(connPool.getSession(target, connectTimeout, new FutureCallback() { @Override diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2ConnPool.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2ConnPool.java new file mode 100644 index 000000000..5b6ce8662 --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/InternalH2ConnPool.java @@ -0,0 +1,102 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * . + * + */ +package org.apache.hc.client5.http.impl.async; + +import java.net.InetSocketAddress; +import java.util.concurrent.Future; + +import org.apache.hc.client5.http.config.ConnectionConfig; +import org.apache.hc.core5.concurrent.CallbackContribution; +import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.function.Resolver; +import org.apache.hc.core5.http.HttpHost; +import org.apache.hc.core5.http.nio.ssl.TlsStrategy; +import org.apache.hc.core5.http2.nio.pool.H2ConnPool; +import org.apache.hc.core5.io.CloseMode; +import org.apache.hc.core5.io.ModalCloseable; +import org.apache.hc.core5.reactor.ConnectionInitiator; +import org.apache.hc.core5.reactor.IOSession; +import org.apache.hc.core5.util.TimeValue; +import org.apache.hc.core5.util.Timeout; + +class InternalH2ConnPool implements ModalCloseable { + + private final H2ConnPool connPool; + + private volatile Resolver connectionConfigResolver; + + InternalH2ConnPool(final ConnectionInitiator connectionInitiator, + final Resolver addressResolver, + final TlsStrategy tlsStrategy) { + this.connPool = new H2ConnPool(connectionInitiator, addressResolver, tlsStrategy); + } + + public void close(final CloseMode closeMode) { + connPool.close(closeMode); + } + + public void close() { + connPool.close(); + } + + private ConnectionConfig resolveConnectionConfig(final HttpHost httpHost) { + final Resolver resolver = this.connectionConfigResolver; + final ConnectionConfig connectionConfig = resolver != null ? resolver.resolve(httpHost) : null; + return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + } + + public Future getSession( + final HttpHost endpoint, + final Timeout connectTimeout, + final FutureCallback callback) { + final ConnectionConfig connectionConfig = resolveConnectionConfig(endpoint); + return connPool.getSession( + endpoint, + connectTimeout != null ? connectTimeout : connectionConfig.getConnectTimeout(), + new CallbackContribution(callback) { + + @Override + public void completed(final IOSession ioSession) { + final Timeout socketTimeout = connectionConfig.getSocketTimeout(); + if (socketTimeout != null) { + ioSession.setSocketTimeout(socketTimeout); + } + callback.completed(ioSession); + } + + }); + } + + public void closeIdle(final TimeValue idleTime) { + connPool.closeIdle(idleTime); + } + + public void setConnectionConfigResolver(final Resolver connectionConfigResolver) { + this.connectionConfigResolver = connectionConfigResolver; + } + +} diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java index 6d91ff825..fe0f7fd75 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/async/MinimalH2AsyncClient.java @@ -35,6 +35,7 @@ import java.util.concurrent.ThreadFactory; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.config.Configurable; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ExecSupport; @@ -46,6 +47,7 @@ import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.Cancellable; import org.apache.hc.core5.concurrent.ComplexCancellable; import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.EntityDetails; import org.apache.hc.core5.http.Header; import org.apache.hc.core5.http.HttpException; @@ -61,7 +63,6 @@ import org.apache.hc.core5.http.nio.command.RequestExecutionCommand; import org.apache.hc.core5.http.nio.command.ShutdownCommand; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.http.protocol.HttpContext; -import org.apache.hc.core5.http2.nio.pool.H2ConnPool; import org.apache.hc.core5.io.CloseMode; import org.apache.hc.core5.reactor.Command; import org.apache.hc.core5.reactor.ConnectionInitiator; @@ -90,7 +91,7 @@ import org.slf4j.LoggerFactory; public final class MinimalH2AsyncClient extends AbstractMinimalHttpAsyncClientBase { private static final Logger LOG = LoggerFactory.getLogger(MinimalH2AsyncClient.class); - private final H2ConnPool connPool; + private final InternalH2ConnPool connPool; private final ConnectionInitiator connectionInitiator; MinimalH2AsyncClient( @@ -112,7 +113,7 @@ public final class MinimalH2AsyncClient extends AbstractMinimalHttpAsyncClientBa pushConsumerRegistry, threadFactory); this.connectionInitiator = new MultihomeConnectionInitiator(getConnectionInitiator(), dnsResolver); - this.connPool = new H2ConnPool(this.connectionInitiator, object -> null, tlsStrategy); + this.connPool = new InternalH2ConnPool(this.connectionInitiator, object -> null, tlsStrategy); } @Override @@ -249,4 +250,13 @@ public final class MinimalH2AsyncClient extends AbstractMinimalHttpAsyncClientBa return cancellable; } + /** + * Sets {@link Resolver} for {@link ConnectionConfig} on a per host basis. + * + * @since 5.2 + */ + public void setConnectionConfigResolver(final Resolver connectionConfigResolver) { + connPool.setConnectionConfigResolver(connectionConfigResolver); + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java index 230c304ab..a529dfbc2 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/BasicHttpClientConnectionManager.java @@ -38,6 +38,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ConnectionShutdownException; import org.apache.hc.client5.http.io.ConnectionEndpoint; @@ -107,6 +108,7 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan private long expiry; private boolean leased; private SocketConfig socketConfig; + private ConnectionConfig connectionConfig; private final AtomicBoolean closed; @@ -187,6 +189,20 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; } + /** + * @since 5.2 + */ + public synchronized ConnectionConfig getConnectionConfig() { + return connectionConfig; + } + + /** + * @since 5.2 + */ + public synchronized void setConnectionConfig(final ConnectionConfig connectionConfig) { + this.connectionConfig = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + } + public LeaseRequest lease(final String id, final HttpRoute route, final Object state) { return lease(id, route, Timeout.DISABLED, state); } @@ -328,7 +344,7 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan } @Override - public void connect(final ConnectionEndpoint endpoint, final TimeValue connectTimeout, final HttpContext context) throws IOException { + public synchronized void connect(final ConnectionEndpoint endpoint, final TimeValue timeout, final HttpContext context) throws IOException { Args.notNull(endpoint, "Endpoint"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint); @@ -342,17 +358,24 @@ public class BasicHttpClientConnectionManager implements HttpClientConnectionMan } else { host = route.getTargetHost(); } + final ConnectionConfig config = connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + final TimeValue connectTimeout = timeout != null ? timeout : config.getConnectTimeout(); + final ManagedHttpClientConnection connection = internalEndpoint.getConnection(); this.connectionOperator.connect( - internalEndpoint.getConnection(), + connection, host, route.getLocalSocketAddress(), connectTimeout, this.socketConfig, context); + final Timeout socketTimeout = config.getSocketTimeout(); + if (socketTimeout != null) { + connection.setSocketTimeout(socketTimeout); + } } @Override - public void upgrade( + public synchronized void upgrade( final ConnectionEndpoint endpoint, final HttpContext context) throws IOException { Args.notNull(endpoint, "Endpoint"); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java index 13b399847..00b40e1e9 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManager.java @@ -39,6 +39,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ConnectionShutdownException; import org.apache.hc.client5.http.io.ConnectionEndpoint; @@ -52,6 +53,7 @@ import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; import org.apache.hc.core5.annotation.Contract; import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; +import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.ClassicHttpRequest; import org.apache.hc.core5.http.ClassicHttpResponse; import org.apache.hc.core5.http.HttpException; @@ -112,8 +114,8 @@ public class PoolingHttpClientConnectionManager private final HttpConnectionFactory connFactory; private final AtomicBoolean closed; - private volatile SocketConfig defaultSocketConfig; - private volatile TimeValue validateAfterInactivity; + private volatile Resolver socketConfigResolver; + private volatile Resolver connectionConfigResolver; public PoolingHttpClientConnectionManager() { this(RegistryBuilder.create() @@ -203,7 +205,6 @@ public class PoolingHttpClientConnectionManager } this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; this.closed = new AtomicBoolean(false); - this.validateAfterInactivity = TimeValue.ofSeconds(2L); } @Internal @@ -241,6 +242,23 @@ public class PoolingHttpClientConnectionManager throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass()); } + private ConnectionConfig resolveConnectionConfig(final HttpRoute route) { + final Resolver resolver = this.connectionConfigResolver; + final ConnectionConfig connectionConfig = resolver != null ? resolver.resolve(route) : null; + return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + } + + private SocketConfig resolveSocketConfig(final HttpRoute route) { + final Resolver resolver = this.socketConfigResolver; + final SocketConfig socketConfig = resolver != null ? resolver.resolve(route) : null; + return socketConfig != null ? socketConfig : SocketConfig.DEFAULT; + } + + private TimeValue resolveValidateAfterInactivity(final ConnectionConfig connectionConfig) { + final TimeValue timeValue = connectionConfig.getValidateAfterInactivity(); + return timeValue != null ? timeValue : TimeValue.ofSeconds(2); + } + public LeaseRequest lease(final String id, final HttpRoute route, final Object state) { return lease(id, route, Timeout.DISABLED, state); } @@ -280,12 +298,13 @@ public class PoolingHttpClientConnectionManager if (LOG.isDebugEnabled()) { LOG.debug("{} endpoint leased {}", id, ConnPoolSupport.formatStats(route, state, pool)); } + final ConnectionConfig connectionConfig = resolveConnectionConfig(route); + final TimeValue timeValue = resolveValidateAfterInactivity(connectionConfig); try { - final TimeValue validateAfterInactivitySnapshot = validateAfterInactivity; - if (TimeValue.isNonNegative(validateAfterInactivitySnapshot)) { + if (TimeValue.isNonNegative(timeValue)) { final ManagedHttpClientConnection conn = poolEntry.getConnection(); if (conn != null - && poolEntry.getUpdated() + validateAfterInactivitySnapshot.toMilliseconds() <= System.currentTimeMillis()) { + && poolEntry.getUpdated() + timeValue.toMilliseconds() <= System.currentTimeMillis()) { boolean stale; try { stale = conn.isStale(); @@ -382,7 +401,7 @@ public class PoolingHttpClientConnectionManager } @Override - public void connect(final ConnectionEndpoint endpoint, final TimeValue connectTimeout, final HttpContext context) throws IOException { + public void connect(final ConnectionEndpoint endpoint, final TimeValue timeout, final HttpContext context) throws IOException { Args.notNull(endpoint, "Managed endpoint"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint); if (internalEndpoint.isConnected()) { @@ -399,21 +418,27 @@ public class PoolingHttpClientConnectionManager } else { host = route.getTargetHost(); } + final SocketConfig socketConfig = resolveSocketConfig(route); + final ConnectionConfig connectionConfig = resolveConnectionConfig(route); + final TimeValue connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout(); if (LOG.isDebugEnabled()) { LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout); } final ManagedHttpClientConnection conn = poolEntry.getConnection(); - final SocketConfig defaultSocketConfigSnapshot = defaultSocketConfig; this.connectionOperator.connect( conn, host, route.getLocalSocketAddress(), - connectTimeout, - defaultSocketConfigSnapshot != null ? defaultSocketConfigSnapshot : SocketConfig.DEFAULT, + timeout, + socketConfig, context); if (LOG.isDebugEnabled()) { LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn)); } + final Timeout socketTimeout = connectionConfig.getSocketTimeout(); + if (socketTimeout != null) { + conn.setSocketTimeout(socketTimeout); + } } @Override @@ -485,21 +510,56 @@ public class PoolingHttpClientConnectionManager return this.pool.getStats(route); } - public SocketConfig getDefaultSocketConfig() { - return this.defaultSocketConfig; - } - - public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) { - this.defaultSocketConfig = defaultSocketConfig; + /** + * Sets the same {@link SocketConfig} for all routes + */ + public void setDefaultSocketConfig(final SocketConfig config) { + this.socketConfigResolver = (route) -> config; } /** - * @see #setValidateAfterInactivity(TimeValue) + * Sets {@link Resolver} of {@link SocketConfig} on a per route basis. * - * @since 4.4 + * @since 5.2 */ + public void setSocketConfigResolver(final Resolver socketConfigResolver) { + this.socketConfigResolver = socketConfigResolver; + } + + /** + * Sets the same {@link ConnectionConfig} for all routes + * + * @since 5.2 + */ + public void setDefaultConnectionConfig(final ConnectionConfig config) { + this.connectionConfigResolver = (route) -> config; + } + + /** + * Sets {@link Resolver} of {@link ConnectionConfig} on a per route basis. + * + * @since 5.2 + */ + public void setConnectionConfigResolver(final Resolver connectionConfigResolver) { + this.connectionConfigResolver = connectionConfigResolver; + } + + /** + * @deprecated Use custom {@link #setConnectionConfigResolver(Resolver)} + */ + @Deprecated + public SocketConfig getDefaultSocketConfig() { + return SocketConfig.DEFAULT; + } + + /** + * @since 4.4 + * + * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}. + */ + @Deprecated public TimeValue getValidateAfterInactivity() { - return validateAfterInactivity; + return ConnectionConfig.DEFAULT.getValidateAfterInactivity(); } /** @@ -509,9 +569,14 @@ public class PoolingHttpClientConnectionManager * detect connections that have become stale (half-closed) while kept inactive in the pool. * * @since 4.4 + * + * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}. */ + @Deprecated public void setValidateAfterInactivity(final TimeValue validateAfterInactivity) { - this.validateAfterInactivity = validateAfterInactivity; + setDefaultConnectionConfig(ConnectionConfig.custom() + .setValidateAfterInactivity(validateAfterInactivity) + .build()); } private static final AtomicLong COUNT = new AtomicLong(0); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java index 2dd506bf3..e52f9acc7 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/io/PoolingHttpClientConnectionManagerBuilder.java @@ -28,12 +28,15 @@ package org.apache.hc.client5.http.impl.io; import org.apache.hc.client5.http.DnsResolver; +import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.io.ManagedHttpClientConnection; import org.apache.hc.client5.http.socket.ConnectionSocketFactory; import org.apache.hc.client5.http.socket.LayeredConnectionSocketFactory; import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory; import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory; +import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.URIScheme; import org.apache.hc.core5.http.config.RegistryBuilder; import org.apache.hc.core5.http.io.HttpConnectionFactory; @@ -76,7 +79,8 @@ public class PoolingHttpClientConnectionManagerBuilder { private DnsResolver dnsResolver; private PoolConcurrencyPolicy poolConcurrencyPolicy; private PoolReusePolicy poolReusePolicy; - private SocketConfig defaultSocketConfig; + private Resolver socketConfigResolver; + private Resolver connectionConfigResolver; private boolean systemProperties; @@ -84,7 +88,6 @@ public class PoolingHttpClientConnectionManagerBuilder { private int maxConnPerRoute; private TimeValue timeToLive; - private TimeValue validateAfterInactivity; public static PoolingHttpClientConnectionManagerBuilder create() { return new PoolingHttpClientConnectionManagerBuilder(); @@ -161,10 +164,42 @@ public class PoolingHttpClientConnectionManagerBuilder { } /** - * Assigns default {@link SocketConfig}. + * Assigns the same {@link SocketConfig} for all routes. */ public final PoolingHttpClientConnectionManagerBuilder setDefaultSocketConfig(final SocketConfig config) { - this.defaultSocketConfig = config; + this.socketConfigResolver = (route) -> config; + return this; + } + + /** + * Assigns {@link Resolver} of {@link SocketConfig} on a per route basis. + * + * @since 5.2 + */ + public final PoolingHttpClientConnectionManagerBuilder setSocketConfigResolver( + final Resolver socketConfigResolver) { + this.socketConfigResolver = socketConfigResolver; + return this; + } + + /** + * Assigns the same {@link ConnectionConfig} for all routes. + * + * @since 5.2 + */ + public final PoolingHttpClientConnectionManagerBuilder setDefaultConnectionConfig(final ConnectionConfig config) { + this.connectionConfigResolver = (route) -> config; + return this; + } + + /** + * Assigns {@link Resolver} of {@link ConnectionConfig} on a per route basis. + * + * @since 5.2 + */ + public final PoolingHttpClientConnectionManagerBuilder setConnectionConfigResolver( + final Resolver connectionConfigResolver) { + this.connectionConfigResolver = connectionConfigResolver; return this; } @@ -180,10 +215,13 @@ public class PoolingHttpClientConnectionManagerBuilder { * Sets period after inactivity after which persistent * connections must be checked to ensure they are still valid. * - * @see org.apache.hc.core5.http.io.HttpClientConnection#isStale() + * @deprecated Use {@link #setDefaultConnectionConfig(ConnectionConfig)}. */ + @Deprecated public final PoolingHttpClientConnectionManagerBuilder setValidateAfterInactivity(final TimeValue validateAfterInactivity) { - this.validateAfterInactivity = validateAfterInactivity; + setDefaultConnectionConfig(ConnectionConfig.custom() + .setValidateAfterInactivity(validateAfterInactivity) + .build()); return this; } @@ -197,8 +235,7 @@ public class PoolingHttpClientConnectionManagerBuilder { } public PoolingHttpClientConnectionManager build() { - @SuppressWarnings("resource") - final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager( + @SuppressWarnings("resource") final PoolingHttpClientConnectionManager poolingmgr = new PoolingHttpClientConnectionManager( RegistryBuilder.create() .register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory()) .register(URIScheme.HTTPS.id, sslSocketFactory != null ? sslSocketFactory : @@ -212,12 +249,8 @@ public class PoolingHttpClientConnectionManagerBuilder { schemePortResolver, dnsResolver, connectionFactory); - if (validateAfterInactivity != null) { - poolingmgr.setValidateAfterInactivity(validateAfterInactivity); - } - if (defaultSocketConfig != null) { - poolingmgr.setDefaultSocketConfig(defaultSocketConfig); - } + poolingmgr.setSocketConfigResolver(socketConfigResolver); + poolingmgr.setConnectionConfigResolver(connectionConfigResolver); if (maxConnTotal > 0) { poolingmgr.setMaxTotal(maxConnTotal); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java index dc59ce85c..53c34ab62 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManager.java @@ -37,6 +37,7 @@ import java.util.concurrent.atomic.AtomicReference; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.impl.ConnPoolSupport; import org.apache.hc.client5.http.impl.ConnectionShutdownException; import org.apache.hc.client5.http.nio.AsyncClientConnectionManager; @@ -49,6 +50,7 @@ import org.apache.hc.core5.annotation.Internal; import org.apache.hc.core5.annotation.ThreadingBehavior; import org.apache.hc.core5.concurrent.ComplexFuture; import org.apache.hc.core5.concurrent.FutureCallback; +import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.http.HttpVersion; import org.apache.hc.core5.http.ProtocolVersion; @@ -111,7 +113,7 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio private final AsyncClientConnectionOperator connectionOperator; private final AtomicBoolean closed; - private volatile TimeValue validateAfterInactivity; + private volatile Resolver connectionConfigResolver; public PoolingAsyncClientConnectionManager() { this(RegistryBuilder.create() @@ -210,6 +212,12 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass()); } + private ConnectionConfig resolveConnectionConfig(final HttpRoute route) { + final Resolver resolver = this.connectionConfigResolver; + final ConnectionConfig connectionConfig = resolver != null ? resolver.resolve(route) : null; + return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT; + } + @Override public Future lease( final String id, @@ -221,6 +229,7 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio LOG.debug("{} endpoint lease request ({}) {}", id, requestTimeout, ConnPoolSupport.formatStats(route, state, pool)); } final ComplexFuture resultFuture = new ComplexFuture<>(callback); + final ConnectionConfig connectionConfig = resolveConnectionConfig(route); final Future> leaseFuture = pool.lease( route, state, requestTimeout, new FutureCallback>() { @@ -242,25 +251,19 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio @Override public void completed(final PoolEntry poolEntry) { final ManagedAsyncClientConnection connection = poolEntry.getConnection(); - if (connection != null) { - if (connection.isOpen()) { - final ProtocolVersion protocolVersion = connection.getProtocolVersion(); - if (protocolVersion != null && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) { - final TimeValue timeValue = PoolingAsyncClientConnectionManager.this.validateAfterInactivity; - if (TimeValue.isNonNegative(timeValue) && - poolEntry.getUpdated() + timeValue.toMilliseconds() <= System.currentTimeMillis()) { - connection.submitCommand(new PingCommand(new BasicPingHandler(result -> { - if (result == null || !result) { - if (LOG.isDebugEnabled()) { - LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(connection)); - } - poolEntry.discardConnection(CloseMode.IMMEDIATE); - } - leaseCompleted(poolEntry); - })), Command.Priority.IMMEDIATE); - return; + final TimeValue timeValue = connectionConfig != null ? connectionConfig.getValidateAfterInactivity() : null; + if (TimeValue.isNonNegative(timeValue) && connection != null && + poolEntry.getUpdated() + timeValue.toMilliseconds() <= System.currentTimeMillis()) { + final ProtocolVersion protocolVersion = connection.getProtocolVersion(); + if (protocolVersion != null && protocolVersion.greaterEquals(HttpVersion.HTTP_2_0)) { + connection.submitCommand(new PingCommand(new BasicPingHandler(result -> { + if (result == null || !result) { + if (LOG.isDebugEnabled()) { + LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(connection)); + } + poolEntry.discardConnection(CloseMode.IMMEDIATE); } - } + })), Command.Priority.IMMEDIATE); } else { if (LOG.isDebugEnabled()) { LOG.debug("{} connection {} is closed", id, ConnPoolSupport.getId(connection)); @@ -336,13 +339,12 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio public Future connect( final AsyncConnectionEndpoint endpoint, final ConnectionInitiator connectionInitiator, - final Timeout connectTimeout, + final Timeout timeout, final Object attachment, final HttpContext context, final FutureCallback callback) { Args.notNull(endpoint, "Endpoint"); Args.notNull(connectionInitiator, "Connection initiator"); - Args.notNull(connectTimeout, "Timeout"); final InternalConnectionEndpoint internalEndpoint = cast(endpoint); final ComplexFuture resultFuture = new ComplexFuture<>(callback); if (internalEndpoint.isConnected()) { @@ -358,6 +360,9 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio host = route.getTargetHost(); } final InetSocketAddress localAddress = route.getLocalSocketAddress(); + final ConnectionConfig connectionConfig = resolveConnectionConfig(route); + final Timeout connectTimeout = timeout != null ? timeout : connectionConfig.getConnectTimeout(); + if (LOG.isDebugEnabled()) { LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout); } @@ -370,6 +375,10 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio if (LOG.isDebugEnabled()) { LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(connection)); } + final Timeout socketTimeout = connectionConfig.getSocketTimeout(); + if (socketTimeout != null) { + connection.setSocketTimeout(socketTimeout); + } poolEntry.assignConnection(connection); resultFuture.completed(internalEndpoint); } catch (final RuntimeException ex) { @@ -463,8 +472,30 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio return pool.getStats(route); } + /** + * Sets the same {@link ConnectionConfig} for all routes + * + * @since 5.2 + */ + public void setDefaultConnectionConfig(final ConnectionConfig config) { + this.connectionConfigResolver = (route) -> config; + } + + /** + * Sets {@link Resolver} of {@link ConnectionConfig} on a per route basis. + * + * @since 5.2 + */ + public void setConnectionConfigResolver(final Resolver connectionConfigResolver) { + this.connectionConfigResolver = connectionConfigResolver; + } + + /** + * @deprecated Use custom {@link #setConnectionConfigResolver(Resolver)} + */ + @Deprecated public TimeValue getValidateAfterInactivity() { - return validateAfterInactivity; + return ConnectionConfig.DEFAULT.getValidateAfterInactivity(); } /** @@ -473,9 +504,14 @@ public class PoolingAsyncClientConnectionManager implements AsyncClientConnectio * FutureCallback)} leased} to the consumer. Negative values passed * to this method disable connection validation. This check helps detect connections * that have become stale (half-closed) while kept inactive in the pool. + * + * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}. */ + @Deprecated public void setValidateAfterInactivity(final TimeValue validateAfterInactivity) { - this.validateAfterInactivity = validateAfterInactivity; + setDefaultConnectionConfig(ConnectionConfig.custom() + .setValidateAfterInactivity(validateAfterInactivity) + .build()); } private static final AtomicLong COUNT = new AtomicLong(0); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java index c1dd8ae07..a8d44b0fa 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/impl/nio/PoolingAsyncClientConnectionManagerBuilder.java @@ -28,10 +28,14 @@ package org.apache.hc.client5.http.impl.nio; import org.apache.hc.client5.http.DnsResolver; +import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SchemePortResolver; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.ssl.ConscryptClientTlsStrategy; import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy; +import org.apache.hc.core5.function.Resolver; import org.apache.hc.core5.http.config.RegistryBuilder; +import org.apache.hc.core5.http.io.SocketConfig; import org.apache.hc.core5.http.nio.ssl.TlsStrategy; import org.apache.hc.core5.pool.PoolConcurrencyPolicy; import org.apache.hc.core5.pool.PoolReusePolicy; @@ -77,8 +81,9 @@ public class PoolingAsyncClientConnectionManagerBuilder { private int maxConnTotal; private int maxConnPerRoute; + private Resolver socketConfigResolver; + private Resolver connectionConfigResolver; private TimeValue timeToLive; - private TimeValue validateAfterInactivity; public static PoolingAsyncClientConnectionManagerBuilder create() { return new PoolingAsyncClientConnectionManagerBuilder(); @@ -145,6 +150,28 @@ public class PoolingAsyncClientConnectionManagerBuilder { return this; } + /** + * Assigns the same {@link ConnectionConfig} for all routes. + * + * @since 5.2 + */ + public final PoolingAsyncClientConnectionManagerBuilder setDefaultConnectionConfig(final ConnectionConfig config) { + this.connectionConfigResolver = (route) -> config; + return this; + } + + /** + * Assigns {@link Resolver} of {@link ConnectionConfig} on a per route basis. + * + * @since 5.2 + */ + public final PoolingAsyncClientConnectionManagerBuilder setConnectionConfigResolver( + final Resolver connectionConfigResolver) { + this.connectionConfigResolver = connectionConfigResolver; + return this; + } + + /** * Sets maximum time to live for persistent connections */ @@ -157,10 +184,13 @@ public class PoolingAsyncClientConnectionManagerBuilder { * Sets period after inactivity after which persistent * connections must be checked to ensure they are still valid. * - * @see org.apache.hc.core5.http.io.HttpClientConnection#isStale() + * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}. */ + @Deprecated public final PoolingAsyncClientConnectionManagerBuilder setValidateAfterInactivity(final TimeValue validateAfterInactivity) { - this.validateAfterInactivity = validateAfterInactivity; + setDefaultConnectionConfig(ConnectionConfig.custom() + .setValidateAfterInactivity(validateAfterInactivity) + .build()); return this; } @@ -201,9 +231,7 @@ public class PoolingAsyncClientConnectionManagerBuilder { timeToLive, schemePortResolver, dnsResolver); - if (validateAfterInactivity != null) { - poolingmgr.setValidateAfterInactivity(validateAfterInactivity); - } + poolingmgr.setConnectionConfigResolver(connectionConfigResolver); if (maxConnTotal > 0) { poolingmgr.setMaxTotal(maxConnTotal); } diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/config/TestRequestConfig.java b/httpclient5/src/test/java/org/apache/hc/client5/http/config/TestRequestConfig.java index e0035d813..572bba0d4 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/config/TestRequestConfig.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/config/TestRequestConfig.java @@ -32,7 +32,6 @@ import java.util.concurrent.TimeUnit; import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.cookie.StandardCookieSpec; -import org.apache.hc.core5.http.HttpHost; import org.apache.hc.core5.util.TimeValue; import org.apache.hc.core5.util.Timeout; import org.junit.Assert; @@ -49,7 +48,6 @@ public class TestRequestConfig { @Test public void testDefaults() { final RequestConfig config = RequestConfig.DEFAULT; - Assert.assertEquals(Timeout.ofMinutes(3), config.getConnectTimeout()); Assert.assertEquals(Timeout.ofMinutes(3), config.getConnectionRequestTimeout()); Assert.assertEquals(false, config.isExpectContinueEnabled()); Assert.assertEquals(true, config.isAuthenticationEnabled()); @@ -66,7 +64,6 @@ public class TestRequestConfig { @Test public void testBuildAndCopy() throws Exception { final RequestConfig config0 = RequestConfig.custom() - .setConnectTimeout(33, TimeUnit.MILLISECONDS) .setConnectionRequestTimeout(44, TimeUnit.MILLISECONDS) .setExpectContinueEnabled(true) .setAuthenticationEnabled(false) @@ -74,13 +71,11 @@ public class TestRequestConfig { .setCircularRedirectsAllowed(true) .setMaxRedirects(100) .setCookieSpec(StandardCookieSpec.STRICT) - .setProxy(new HttpHost("someproxy")) .setTargetPreferredAuthSchemes(Collections.singletonList(StandardAuthScheme.NTLM)) .setProxyPreferredAuthSchemes(Collections.singletonList(StandardAuthScheme.DIGEST)) .setContentCompressionEnabled(false) .build(); final RequestConfig config = RequestConfig.copy(config0).build(); - Assert.assertEquals(TimeValue.ofMilliseconds(33), config.getConnectTimeout()); Assert.assertEquals(TimeValue.ofMilliseconds(44), config.getConnectionRequestTimeout()); Assert.assertEquals(true, config.isExpectContinueEnabled()); Assert.assertEquals(false, config.isAuthenticationEnabled()); @@ -88,7 +83,6 @@ public class TestRequestConfig { Assert.assertEquals(true, config.isCircularRedirectsAllowed()); Assert.assertEquals(100, config.getMaxRedirects()); Assert.assertEquals(StandardCookieSpec.STRICT, config.getCookieSpec()); - Assert.assertEquals(new HttpHost("someproxy"), config.getProxy()); Assert.assertEquals(Collections.singletonList(StandardAuthScheme.NTLM), config.getTargetPreferredAuthSchemes()); Assert.assertEquals(Collections.singletonList(StandardAuthScheme.DIGEST), config.getProxyPreferredAuthSchemes()); Assert.assertEquals(false, config.isContentCompressionEnabled()); diff --git a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java index 7a61c7f14..92a494e97 100644 --- a/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java +++ b/httpclient5/src/test/java/org/apache/hc/client5/http/examples/ClientConfiguration.java @@ -39,13 +39,14 @@ import javax.net.ssl.SSLContext; import org.apache.hc.client5.http.DnsResolver; import org.apache.hc.client5.http.HttpRoute; import org.apache.hc.client5.http.SystemDefaultDnsResolver; -import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.auth.CredentialsProvider; +import org.apache.hc.client5.http.auth.StandardAuthScheme; import org.apache.hc.client5.http.classic.methods.HttpGet; +import org.apache.hc.client5.http.config.ConnectionConfig; import org.apache.hc.client5.http.config.RequestConfig; import org.apache.hc.client5.http.cookie.BasicCookieStore; -import org.apache.hc.client5.http.cookie.StandardCookieSpec; import org.apache.hc.client5.http.cookie.CookieStore; +import org.apache.hc.client5.http.cookie.StandardCookieSpec; import org.apache.hc.client5.http.impl.auth.BasicCredentialsProvider; import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; @@ -172,15 +173,17 @@ public class ClientConfiguration { socketFactoryRegistry, PoolConcurrencyPolicy.STRICT, PoolReusePolicy.LIFO, TimeValue.ofMinutes(5), null, dnsResolver, null); - // Create socket configuration - final SocketConfig socketConfig = SocketConfig.custom() - .setTcpNoDelay(true) - .build(); // Configure the connection manager to use socket configuration either // by default or for a specific host. - connManager.setDefaultSocketConfig(socketConfig); - // Validate connections after 1 sec of inactivity - connManager.setValidateAfterInactivity(TimeValue.ofSeconds(10)); + connManager.setDefaultSocketConfig(SocketConfig.custom() + .setTcpNoDelay(true) + .build()); + // Validate connections after 10 sec of inactivity + connManager.setDefaultConnectionConfig(ConnectionConfig.custom() + .setConnectTimeout(Timeout.ofSeconds(30)) + .setSocketTimeout(Timeout.ofSeconds(30)) + .setValidateAfterInactivity(TimeValue.ofSeconds(10)) + .build()); // Configure total max or per route limits for persistent connections // that can be kept in the pool or leased by the connection manager. @@ -214,8 +217,6 @@ public class ClientConfiguration { // They will take precedence over the one set at the client level. final RequestConfig requestConfig = RequestConfig.copy(defaultRequestConfig) .setConnectionRequestTimeout(Timeout.ofSeconds(5)) - .setConnectTimeout(Timeout.ofSeconds(5)) - .setProxy(new HttpHost("myotherproxy", 8080)) .build(); httpget.setConfig(requestConfig);