View Javadoc
1   /*
2    * ====================================================================
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing,
14   * software distributed under the License is distributed on an
15   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16   * KIND, either express or implied.  See the License for the
17   * specific language governing permissions and limitations
18   * under the License.
19   * ====================================================================
20   *
21   * This software consists of voluntary contributions made by many
22   * individuals on behalf of the Apache Software Foundation.  For more
23   * information on the Apache Software Foundation, please see
24   * <http://www.apache.org/>.
25   *
26   */
27  package org.apache.hc.client5.http.impl.io;
28  
29  import java.io.IOException;
30  import java.util.Set;
31  import java.util.concurrent.ExecutionException;
32  import java.util.concurrent.Future;
33  import java.util.concurrent.TimeoutException;
34  import java.util.concurrent.atomic.AtomicBoolean;
35  import java.util.concurrent.atomic.AtomicReference;
36  import java.util.concurrent.locks.ReentrantLock;
37  
38  import org.apache.hc.client5.http.DnsResolver;
39  import org.apache.hc.client5.http.EndpointInfo;
40  import org.apache.hc.client5.http.HttpRoute;
41  import org.apache.hc.client5.http.SchemePortResolver;
42  import org.apache.hc.client5.http.config.ConnectionConfig;
43  import org.apache.hc.client5.http.config.TlsConfig;
44  import org.apache.hc.client5.http.impl.ConnPoolSupport;
45  import org.apache.hc.client5.http.impl.ConnectionHolder;
46  import org.apache.hc.client5.http.impl.ConnectionShutdownException;
47  import org.apache.hc.client5.http.impl.PrefixedIncrementingId;
48  import org.apache.hc.client5.http.io.ConnectionEndpoint;
49  import org.apache.hc.client5.http.io.HttpClientConnectionManager;
50  import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
51  import org.apache.hc.client5.http.io.LeaseRequest;
52  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
53  import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
54  import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
55  import org.apache.hc.core5.annotation.Contract;
56  import org.apache.hc.core5.annotation.Internal;
57  import org.apache.hc.core5.annotation.ThreadingBehavior;
58  import org.apache.hc.core5.function.Resolver;
59  import org.apache.hc.core5.http.ClassicHttpRequest;
60  import org.apache.hc.core5.http.ClassicHttpResponse;
61  import org.apache.hc.core5.http.HttpConnection;
62  import org.apache.hc.core5.http.HttpException;
63  import org.apache.hc.core5.http.HttpHost;
64  import org.apache.hc.core5.http.URIScheme;
65  import org.apache.hc.core5.http.config.Registry;
66  import org.apache.hc.core5.http.config.RegistryBuilder;
67  import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
68  import org.apache.hc.core5.http.io.HttpConnectionFactory;
69  import org.apache.hc.core5.http.io.SocketConfig;
70  import org.apache.hc.core5.http.protocol.HttpContext;
71  import org.apache.hc.core5.io.CloseMode;
72  import org.apache.hc.core5.pool.ConnPoolControl;
73  import org.apache.hc.core5.pool.DefaultDisposalCallback;
74  import org.apache.hc.core5.pool.LaxConnPool;
75  import org.apache.hc.core5.pool.ManagedConnPool;
76  import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
77  import org.apache.hc.core5.pool.PoolEntry;
78  import org.apache.hc.core5.pool.PoolReusePolicy;
79  import org.apache.hc.core5.pool.PoolStats;
80  import org.apache.hc.core5.pool.StrictConnPool;
81  import org.apache.hc.core5.util.Args;
82  import org.apache.hc.core5.util.Deadline;
83  import org.apache.hc.core5.util.Identifiable;
84  import org.apache.hc.core5.util.TimeValue;
85  import org.apache.hc.core5.util.Timeout;
86  import org.slf4j.Logger;
87  import org.slf4j.LoggerFactory;
88  
89  /**
90   * {@code PoolingHttpClientConnectionManager} maintains a pool of
91   * {@link ManagedHttpClientConnection}s and is able to service connection requests
92   * from multiple execution threads. Connections are pooled on a per route
93   * basis. A request for a route which already the manager has persistent
94   * connections for available in the pool will be serviced by leasing
95   * a connection from the pool rather than creating a new connection.
96   * <p>
97   * {@code PoolingHttpClientConnectionManager} maintains a maximum limit of connections
98   * per route and in total. Connection limits, however, can be adjusted
99   * using {@link ConnPoolControl} methods.
100  * <p>
101  * Total time to live (TTL) set at construction time defines maximum life span
102  * of persistent connections regardless of their expiration setting. No persistent
103  * connection will be re-used past its TTL value.
104  *
105  * @since 4.3
106  */
107 @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
108 public class PoolingHttpClientConnectionManager
109     implements HttpClientConnectionManager, ConnPoolControl<HttpRoute> {
110 
111     private static final Logger LOG = LoggerFactory.getLogger(PoolingHttpClientConnectionManager.class);
112 
113     public static final int DEFAULT_MAX_TOTAL_CONNECTIONS = 25;
114     public static final int DEFAULT_MAX_CONNECTIONS_PER_ROUTE = 5;
115 
116     private final HttpClientConnectionOperator connectionOperator;
117     private final ManagedConnPool<HttpRoute, ManagedHttpClientConnection> pool;
118     private final HttpConnectionFactory<ManagedHttpClientConnection> connFactory;
119     private final AtomicBoolean closed;
120 
121     private volatile Resolver<HttpRoute, SocketConfig> socketConfigResolver;
122     private volatile Resolver<HttpRoute, ConnectionConfig> connectionConfigResolver;
123     private volatile Resolver<HttpHost, TlsConfig> tlsConfigResolver;
124 
125     public PoolingHttpClientConnectionManager() {
126         this(new DefaultHttpClientConnectionOperator(null, null,
127                 RegistryBuilder.<TlsSocketStrategy>create()
128                     .register(URIScheme.HTTPS.id, DefaultClientTlsStrategy.createDefault())
129                     .build()),
130                 PoolConcurrencyPolicy.STRICT,
131                 PoolReusePolicy.LIFO,
132                 TimeValue.NEG_ONE_MILLISECOND,
133                 null);
134     }
135 
136     /**
137      * @deprecated Use {@link PoolingHttpClientConnectionManagerBuilder}
138      */
139     @Deprecated
140     public PoolingHttpClientConnectionManager(
141             final Registry<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry) {
142         this(socketFactoryRegistry, null);
143     }
144 
145     /**
146      * @deprecated Use {@link PoolingHttpClientConnectionManagerBuilder}
147      */
148     @Deprecated
149     public PoolingHttpClientConnectionManager(
150             final Registry<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry,
151             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
152         this(socketFactoryRegistry, PoolConcurrencyPolicy.STRICT, TimeValue.NEG_ONE_MILLISECOND, connFactory);
153     }
154 
155     /**
156      * @deprecated Use {@link PoolingHttpClientConnectionManagerBuilder}
157      */
158     @Deprecated
159     public PoolingHttpClientConnectionManager(
160             final Registry<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry,
161             final PoolConcurrencyPolicy poolConcurrencyPolicy,
162             final TimeValue timeToLive,
163             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
164         this(socketFactoryRegistry, poolConcurrencyPolicy, PoolReusePolicy.LIFO, timeToLive, connFactory);
165     }
166 
167     /**
168      * @deprecated Use {@link PoolingHttpClientConnectionManagerBuilder}
169      */
170     @Deprecated
171     public PoolingHttpClientConnectionManager(
172             final Registry<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry,
173             final PoolConcurrencyPolicy poolConcurrencyPolicy,
174             final PoolReusePolicy poolReusePolicy,
175             final TimeValue timeToLive) {
176         this(socketFactoryRegistry, poolConcurrencyPolicy, poolReusePolicy, timeToLive, null);
177     }
178 
179     /**
180      * @deprecated Use {@link PoolingHttpClientConnectionManagerBuilder}
181      */
182     @Deprecated
183     public PoolingHttpClientConnectionManager(
184             final Registry<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry,
185             final PoolConcurrencyPolicy poolConcurrencyPolicy,
186             final PoolReusePolicy poolReusePolicy,
187             final TimeValue timeToLive,
188             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
189         this(socketFactoryRegistry, poolConcurrencyPolicy, poolReusePolicy, timeToLive, null, null, connFactory);
190     }
191 
192     /**
193      * @deprecated Use {@link PoolingHttpClientConnectionManagerBuilder}
194      */
195     @Deprecated
196     public PoolingHttpClientConnectionManager(
197             final Registry<org.apache.hc.client5.http.socket.ConnectionSocketFactory> socketFactoryRegistry,
198             final PoolConcurrencyPolicy poolConcurrencyPolicy,
199             final PoolReusePolicy poolReusePolicy,
200             final TimeValue timeToLive,
201             final SchemePortResolver schemePortResolver,
202             final DnsResolver dnsResolver,
203             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
204         this(new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
205                 poolConcurrencyPolicy,
206                 poolReusePolicy,
207                 timeToLive,
208                 connFactory);
209     }
210 
211     @Internal
212     public PoolingHttpClientConnectionManager(
213             final HttpClientConnectionOperator httpClientConnectionOperator,
214             final PoolConcurrencyPolicy poolConcurrencyPolicy,
215             final PoolReusePolicy poolReusePolicy,
216             final TimeValue timeToLive,
217             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
218         super();
219         this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
220         switch (poolConcurrencyPolicy != null ? poolConcurrencyPolicy : PoolConcurrencyPolicy.STRICT) {
221             case STRICT:
222                 this.pool = new StrictConnPool<HttpRoute, ManagedHttpClientConnection>(
223                         DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
224                         DEFAULT_MAX_TOTAL_CONNECTIONS,
225                         timeToLive,
226                         poolReusePolicy,
227                         new DefaultDisposalCallback<>(),
228                         null) {
229 
230                     @Override
231                     public void closeExpired() {
232                         enumAvailable(e -> closeIfExpired(e));
233                     }
234 
235                 };
236                 break;
237             case LAX:
238                 this.pool = new LaxConnPool<HttpRoute, ManagedHttpClientConnection>(
239                         DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
240                         timeToLive,
241                         poolReusePolicy,
242                         null) {
243 
244                     @Override
245                     public void closeExpired() {
246                         enumAvailable(e -> closeIfExpired(e));
247                     }
248 
249                 };
250                 break;
251             default:
252                 throw new IllegalArgumentException("Unexpected PoolConcurrencyPolicy value: " + poolConcurrencyPolicy);
253         }
254         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
255         this.closed = new AtomicBoolean(false);
256     }
257 
258     @Internal
259     protected PoolingHttpClientConnectionManager(
260             final HttpClientConnectionOperator httpClientConnectionOperator,
261             final ManagedConnPool<HttpRoute, ManagedHttpClientConnection> pool,
262             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
263         super();
264         this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
265         this.pool = Args.notNull(pool, "Connection pool");
266         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
267         this.closed = new AtomicBoolean(false);
268     }
269 
270     @Override
271     public void close() {
272         close(CloseMode.GRACEFUL);
273     }
274 
275     @Override
276     public void close(final CloseMode closeMode) {
277         if (this.closed.compareAndSet(false, true)) {
278             if (LOG.isDebugEnabled()) {
279                 LOG.debug("Shutdown connection pool {}", closeMode);
280             }
281             this.pool.close(closeMode);
282             LOG.debug("Connection pool shut down");
283         }
284     }
285 
286     private InternalConnectionEndpoint cast(final ConnectionEndpoint endpoint) {
287         if (endpoint instanceof InternalConnectionEndpoint) {
288             return (InternalConnectionEndpoint) endpoint;
289         }
290         throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass());
291     }
292 
293     private SocketConfig resolveSocketConfig(final HttpRoute route) {
294         final Resolver<HttpRoute, SocketConfig> resolver = this.socketConfigResolver;
295         final SocketConfig socketConfig = resolver != null ? resolver.resolve(route) : null;
296         return socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
297     }
298 
299     private ConnectionConfig resolveConnectionConfig(final HttpRoute route) {
300         final Resolver<HttpRoute, ConnectionConfig> resolver = this.connectionConfigResolver;
301         final ConnectionConfig connectionConfig = resolver != null ? resolver.resolve(route) : null;
302         return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
303     }
304 
305     private TlsConfig resolveTlsConfig(final HttpHost host) {
306         final Resolver<HttpHost, TlsConfig> resolver = this.tlsConfigResolver;
307         final TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null;
308         return tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT;
309     }
310 
311     private TimeValue resolveValidateAfterInactivity(final ConnectionConfig connectionConfig) {
312         final TimeValue timeValue = connectionConfig.getValidateAfterInactivity();
313         return timeValue != null ? timeValue : TimeValue.ofSeconds(2);
314     }
315 
316     public LeaseRequest lease(final String id, final HttpRoute route, final Object state) {
317         return lease(id, route, Timeout.DISABLED, state);
318     }
319 
320     @Override
321     public LeaseRequest lease(
322             final String id,
323             final HttpRoute route,
324             final Timeout requestTimeout,
325             final Object state) {
326         Args.notNull(route, "HTTP route");
327         if (LOG.isDebugEnabled()) {
328             LOG.debug("{} endpoint lease request ({}) {}", id, requestTimeout, ConnPoolSupport.formatStats(route, state, pool));
329         }
330         final Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> leaseFuture = this.pool.lease(route, state, requestTimeout, null);
331         return new LeaseRequest() {
332             // Using a ReentrantLock specific to each LeaseRequest instance to maintain the original
333             // synchronization semantics. This ensures that each LeaseRequest has its own unique lock.
334             private final ReentrantLock lock = new ReentrantLock();
335             private volatile ConnectionEndpoint endpoint;
336 
337             @Override
338             public ConnectionEndpoint get(
339                     final Timeout timeout) throws InterruptedException, ExecutionException, TimeoutException {
340                 lock.lock();
341                 try {
342                     Args.notNull(timeout, "Operation timeout");
343                     if (this.endpoint != null) {
344                         return this.endpoint;
345                     }
346                     final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry;
347                     try {
348                         poolEntry = leaseFuture.get(timeout.getDuration(), timeout.getTimeUnit());
349                     } catch (final TimeoutException ex) {
350                         leaseFuture.cancel(true);
351                         throw ex;
352                     }
353                     if (LOG.isDebugEnabled()) {
354                         LOG.debug("{} endpoint leased {}", id, ConnPoolSupport.formatStats(route, state, pool));
355                     }
356                     final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
357                     try {
358                         if (poolEntry.hasConnection()) {
359                             final TimeValue timeToLive = connectionConfig.getTimeToLive();
360                             if (TimeValue.isNonNegative(timeToLive)) {
361                                 if (timeToLive.getDuration() == 0
362                                         || Deadline.calculate(poolEntry.getCreated(), timeToLive).isExpired()) {
363                                     poolEntry.discardConnection(CloseMode.GRACEFUL);
364                                 }
365                             }
366                         }
367                         if (poolEntry.hasConnection()) {
368                             final TimeValue timeValue = resolveValidateAfterInactivity(connectionConfig);
369                             if (TimeValue.isNonNegative(timeValue)) {
370                                 if (timeValue.getDuration() == 0
371                                         || Deadline.calculate(poolEntry.getUpdated(), timeValue).isExpired()) {
372                                     final ManagedHttpClientConnection conn = poolEntry.getConnection();
373                                     boolean stale;
374                                     try {
375                                         stale = conn.isStale();
376                                     } catch (final IOException ignore) {
377                                         stale = true;
378                                     }
379                                     if (stale) {
380                                         if (LOG.isDebugEnabled()) {
381                                             LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(conn));
382                                         }
383                                         poolEntry.discardConnection(CloseMode.IMMEDIATE);
384                                     }
385                                 }
386                             }
387                         }
388                         final ManagedHttpClientConnection conn = poolEntry.getConnection();
389                         if (conn != null) {
390                             conn.activate();
391                             if (connectionConfig.getSocketTimeout() != null) {
392                                 conn.setSocketTimeout(connectionConfig.getSocketTimeout());
393                             }
394                         } else {
395                             poolEntry.assignConnection(connFactory.createConnection(null));
396                         }
397                         this.endpoint = new InternalConnectionEndpoint(poolEntry);
398                         if (LOG.isDebugEnabled()) {
399                             LOG.debug("{} acquired {}", id, ConnPoolSupport.getId(endpoint));
400                         }
401                         return this.endpoint;
402                     } catch (final Exception ex) {
403                         if (LOG.isDebugEnabled()) {
404                             LOG.debug("{} endpoint lease failed", id);
405                         }
406                         pool.release(poolEntry, false);
407                         throw new ExecutionException(ex.getMessage(), ex);
408                     }
409                 } finally {
410                     lock.unlock();
411                 }
412             }
413 
414             @Override
415             public boolean cancel() {
416                 return leaseFuture.cancel(true);
417             }
418 
419         };
420 
421     }
422 
423     @Override
424     public void release(final ConnectionEndpoint endpoint, final Object state, final TimeValue keepAlive) {
425         Args.notNull(endpoint, "Managed endpoint");
426         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = cast(endpoint).detach();
427         if (entry == null) {
428             return;
429         }
430         if (LOG.isDebugEnabled()) {
431             LOG.debug("{} releasing endpoint", ConnPoolSupport.getId(endpoint));
432         }
433 
434         if (this.isClosed()) {
435             return;
436         }
437 
438         final ManagedHttpClientConnection conn = entry.getConnection();
439         if (conn != null && keepAlive == null) {
440             conn.close(CloseMode.GRACEFUL);
441         }
442         boolean reusable = conn != null && conn.isOpen() && conn.isConsistent();
443         try {
444             if (reusable) {
445                 entry.updateState(state);
446                 entry.updateExpiry(keepAlive);
447                 conn.passivate();
448                 if (LOG.isDebugEnabled()) {
449                     final String s;
450                     if (TimeValue.isPositive(keepAlive)) {
451                         s = "for " + keepAlive;
452                     } else {
453                         s = "indefinitely";
454                     }
455                     LOG.debug("{} connection {} can be kept alive {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn), s);
456                 }
457             } else {
458                 if (LOG.isDebugEnabled()) {
459                     if (conn != null && !conn.isConsistent()) {
460                         LOG.debug("{} connection is in an inconsistent state and cannot be kept alive", ConnPoolSupport.getId(endpoint));
461                     } else {
462                         LOG.debug("{} connection is not kept alive", ConnPoolSupport.getId(endpoint));
463                     }
464                 }
465             }
466         } catch (final RuntimeException ex) {
467             reusable = false;
468             throw ex;
469         } finally {
470             this.pool.release(entry, reusable);
471             if (LOG.isDebugEnabled()) {
472                 LOG.debug("{} connection released {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.formatStats(entry.getRoute(), entry.getState(), pool));
473             }
474         }
475     }
476 
477     @Override
478     public void connect(final ConnectionEndpoint endpoint, final TimeValue timeout, final HttpContext context) throws IOException {
479         Args.notNull(endpoint, "Managed endpoint");
480         final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
481         if (internalEndpoint.isConnected()) {
482             return;
483         }
484         final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getPoolEntry();
485         if (!poolEntry.hasConnection()) {
486             poolEntry.assignConnection(connFactory.createConnection(null));
487         }
488         final HttpRoute route = poolEntry.getRoute();
489         final HttpHost firstHop = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost();
490         final SocketConfig socketConfig = resolveSocketConfig(route);
491         final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
492         final Timeout connectTimeout = timeout != null ? Timeout.of(timeout.getDuration(), timeout.getTimeUnit()) : connectionConfig.getConnectTimeout();
493         if (LOG.isDebugEnabled()) {
494             LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), firstHop, connectTimeout);
495         }
496         final ManagedHttpClientConnection conn = poolEntry.getConnection();
497         this.connectionOperator.connect(
498                 conn,
499                 firstHop,
500                 route.getTargetName(),
501                 route.getLocalSocketAddress(),
502                 connectTimeout,
503                 socketConfig,
504                 route.isTunnelled() ? null : resolveTlsConfig(route.getTargetHost()),
505                 context);
506         if (LOG.isDebugEnabled()) {
507             LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn));
508         }
509         final Timeout socketTimeout = connectionConfig.getSocketTimeout();
510         if (socketTimeout != null) {
511             conn.setSocketTimeout(socketTimeout);
512         }
513     }
514 
515     @Override
516     public void upgrade(final ConnectionEndpoint endpoint, final HttpContext context) throws IOException {
517         Args.notNull(endpoint, "Managed endpoint");
518         final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
519         final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
520         final HttpRoute route = poolEntry.getRoute();
521         final HttpHost target = route.getTargetHost();
522         final TlsConfig tlsConfig = resolveTlsConfig(target);
523         this.connectionOperator.upgrade(
524                 poolEntry.getConnection(),
525                 target,
526                 route.getTargetName(),
527                 tlsConfig,
528                 context);
529     }
530 
531     @Override
532     public void closeIdle(final TimeValue idleTime) {
533         Args.notNull(idleTime, "Idle time");
534         if (LOG.isDebugEnabled()) {
535             LOG.debug("Closing connections idle longer than {}", idleTime);
536         }
537         if (isClosed()) {
538             return;
539         }
540         this.pool.closeIdle(idleTime);
541     }
542 
543     @Override
544     public void closeExpired() {
545         if (isClosed()) {
546             return;
547         }
548         LOG.debug("Closing expired connections");
549         this.pool.closeExpired();
550     }
551 
552     @Override
553     public Set<HttpRoute> getRoutes() {
554         return this.pool.getRoutes();
555     }
556 
557     @Override
558     public int getMaxTotal() {
559         return this.pool.getMaxTotal();
560     }
561 
562     @Override
563     public void setMaxTotal(final int max) {
564         this.pool.setMaxTotal(max);
565     }
566 
567     @Override
568     public int getDefaultMaxPerRoute() {
569         return this.pool.getDefaultMaxPerRoute();
570     }
571 
572     @Override
573     public void setDefaultMaxPerRoute(final int max) {
574         this.pool.setDefaultMaxPerRoute(max);
575     }
576 
577     @Override
578     public int getMaxPerRoute(final HttpRoute route) {
579         return this.pool.getMaxPerRoute(route);
580     }
581 
582     @Override
583     public void setMaxPerRoute(final HttpRoute route, final int max) {
584         this.pool.setMaxPerRoute(route, max);
585     }
586 
587     @Override
588     public PoolStats getTotalStats() {
589         return this.pool.getTotalStats();
590     }
591 
592     @Override
593     public PoolStats getStats(final HttpRoute route) {
594         return this.pool.getStats(route);
595     }
596 
597     /**
598      * Sets the same {@link SocketConfig} for all routes
599      */
600     public void setDefaultSocketConfig(final SocketConfig config) {
601         this.socketConfigResolver = route -> config;
602     }
603 
604     /**
605      * Sets {@link Resolver} of {@link SocketConfig} on a per route basis.
606      *
607      * @since 5.2
608      */
609     public void setSocketConfigResolver(final Resolver<HttpRoute, SocketConfig> socketConfigResolver) {
610         this.socketConfigResolver = socketConfigResolver;
611     }
612 
613     /**
614      * Sets the same {@link ConnectionConfig} for all routes
615      *
616      * @since 5.2
617      */
618     public void setDefaultConnectionConfig(final ConnectionConfig config) {
619         this.connectionConfigResolver = route -> config;
620     }
621 
622     /**
623      * Sets {@link Resolver} of {@link ConnectionConfig} on a per route basis.
624      *
625      * @since 5.2
626      */
627     public void setConnectionConfigResolver(final Resolver<HttpRoute, ConnectionConfig> connectionConfigResolver) {
628         this.connectionConfigResolver = connectionConfigResolver;
629     }
630 
631     /**
632      * Sets the same {@link ConnectionConfig} for all hosts
633      *
634      * @since 5.2
635      */
636     public void setDefaultTlsConfig(final TlsConfig config) {
637         this.tlsConfigResolver = host -> config;
638     }
639 
640     /**
641      * Sets {@link Resolver} of {@link TlsConfig} on a per host basis.
642      *
643      * @since 5.2
644      */
645     public void setTlsConfigResolver(final Resolver<HttpHost, TlsConfig> tlsConfigResolver) {
646         this.tlsConfigResolver = tlsConfigResolver;
647     }
648 
649     void closeIfExpired(final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry) {
650         final long now = System.currentTimeMillis();
651         if (entry.getExpiryDeadline().isBefore(now)) {
652             entry.discardConnection(CloseMode.GRACEFUL);
653         } else {
654             final ConnectionConfig connectionConfig = resolveConnectionConfig(entry.getRoute());
655             final TimeValue timeToLive = connectionConfig.getTimeToLive();
656             if (timeToLive != null && Deadline.calculate(entry.getCreated(), timeToLive).isBefore(now)) {
657                 entry.discardConnection(CloseMode.GRACEFUL);
658             }
659         }
660     }
661 
662     /**
663      * @deprecated Use custom {@link #setConnectionConfigResolver(Resolver)}
664      */
665     @Deprecated
666     public SocketConfig getDefaultSocketConfig() {
667         return SocketConfig.DEFAULT;
668     }
669 
670     /**
671      * @since 4.4
672      *
673      * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}.
674      */
675     @Deprecated
676     public TimeValue getValidateAfterInactivity() {
677         return ConnectionConfig.DEFAULT.getValidateAfterInactivity();
678     }
679 
680     /**
681      * Defines period of inactivity after which persistent connections must
682      * be re-validated prior to being {@link #lease(String, HttpRoute, Object)} leased} to the consumer.
683      * Negative values passed to this method disable connection validation. This check helps
684      * detect connections that have become stale (half-closed) while kept inactive in the pool.
685      *
686      * @since 4.4
687      *
688      * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}.
689      */
690     @Deprecated
691     public void setValidateAfterInactivity(final TimeValue validateAfterInactivity) {
692         setDefaultConnectionConfig(ConnectionConfig.custom()
693                 .setValidateAfterInactivity(validateAfterInactivity)
694                 .build());
695     }
696 
697     private static final PrefixedIncrementingId INCREMENTING_ID = new PrefixedIncrementingId("ep-");
698 
699     static class InternalConnectionEndpoint extends ConnectionEndpoint implements ConnectionHolder, Identifiable {
700 
701         private final AtomicReference<PoolEntry<HttpRoute, ManagedHttpClientConnection>> poolEntryRef;
702         private final String id;
703 
704         InternalConnectionEndpoint(
705                 final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry) {
706             this.poolEntryRef = new AtomicReference<>(poolEntry);
707             this.id = INCREMENTING_ID.getNextId();
708         }
709 
710         @Override
711         public String getId() {
712             return id;
713         }
714 
715         PoolEntry<HttpRoute, ManagedHttpClientConnection> getPoolEntry() {
716             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
717             if (poolEntry == null) {
718                 throw new ConnectionShutdownException();
719             }
720             return poolEntry;
721         }
722 
723         PoolEntry<HttpRoute, ManagedHttpClientConnection> getValidatedPoolEntry() {
724             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = getPoolEntry();
725             final ManagedHttpClientConnection connection = poolEntry.getConnection();
726             if (connection == null || !connection.isOpen()) {
727                 throw new ConnectionShutdownException();
728             }
729             return poolEntry;
730         }
731 
732         PoolEntry<HttpRoute, ManagedHttpClientConnection> detach() {
733             return poolEntryRef.getAndSet(null);
734         }
735 
736         @Override
737         public void close(final CloseMode closeMode) {
738             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
739             if (poolEntry != null) {
740                 poolEntry.discardConnection(closeMode);
741             }
742         }
743 
744         @Override
745         public void close() throws IOException {
746             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
747             if (poolEntry != null) {
748                 poolEntry.discardConnection(CloseMode.GRACEFUL);
749             }
750         }
751 
752         @Override
753         public boolean isConnected() {
754             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = getPoolEntry();
755             final ManagedHttpClientConnection connection = poolEntry.getConnection();
756             return connection != null && connection.isOpen();
757         }
758 
759         @Override
760         public void setSocketTimeout(final Timeout timeout) {
761             getValidatedPoolEntry().getConnection().setSocketTimeout(timeout);
762         }
763 
764         /**
765          * @deprecated Use {@link #execute(String, ClassicHttpRequest, RequestExecutor, HttpContext)}
766          */
767         @Deprecated
768         @Override
769         public ClassicHttpResponse execute(
770                 final String exchangeId,
771                 final ClassicHttpRequest request,
772                 final HttpRequestExecutor requestExecutor,
773                 final HttpContext context) throws IOException, HttpException {
774             Args.notNull(request, "HTTP request");
775             Args.notNull(requestExecutor, "Request executor");
776             final ManagedHttpClientConnection connection = getValidatedPoolEntry().getConnection();
777             if (LOG.isDebugEnabled()) {
778                 LOG.debug("{} executing exchange {} over {}", id, exchangeId, ConnPoolSupport.getId(connection));
779             }
780             return requestExecutor.execute(request, connection, context);
781         }
782 
783         /**
784          * @since 5.4
785          */
786         @Override
787         public ClassicHttpResponse execute(
788                 final String exchangeId,
789                 final ClassicHttpRequest request,
790                 final RequestExecutor requestExecutor,
791                 final HttpContext context) throws IOException, HttpException {
792             Args.notNull(request, "HTTP request");
793             Args.notNull(requestExecutor, "Request executor");
794             final ManagedHttpClientConnection connection = getValidatedPoolEntry().getConnection();
795             if (LOG.isDebugEnabled()) {
796                 LOG.debug("{} executing exchange {} over {}", id, exchangeId, ConnPoolSupport.getId(connection));
797             }
798             return requestExecutor.execute(request, connection, context);
799         }
800 
801         /**
802          * @since 5.4
803          */
804         @Override
805         public EndpointInfo getInfo() {
806             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
807             if (poolEntry != null) {
808                 final ManagedHttpClientConnection connection = poolEntry.getConnection();
809                 if (connection != null && connection.isOpen()) {
810                     return new EndpointInfo(connection.getProtocolVersion(), connection.getSSLSession());
811                 }
812             }
813             return null;
814         }
815 
816         @Override
817         public HttpConnection get() {
818             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
819             return poolEntry != null ? poolEntry.getConnection() : null;
820         }
821 
822     }
823 
824     /**
825      * Method that can be called to determine whether the connection manager has been shut down and
826      * is closed or not.
827      *
828      * @return {@code true} if the connection manager has been shut down and is closed, otherwise
829      * return {@code false}.
830      * @since 5.4
831      */
832     public boolean isClosed() {
833         return this.closed.get();
834     }
835 
836 
837 }