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