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  
37  import org.apache.hc.client5.http.DnsResolver;
38  import org.apache.hc.client5.http.HttpRoute;
39  import org.apache.hc.client5.http.SchemePortResolver;
40  import org.apache.hc.client5.http.config.ConnectionConfig;
41  import org.apache.hc.client5.http.config.TlsConfig;
42  import org.apache.hc.client5.http.impl.ConnPoolSupport;
43  import org.apache.hc.client5.http.impl.ConnectionShutdownException;
44  import org.apache.hc.client5.http.impl.PrefixedIncrementingId;
45  import org.apache.hc.client5.http.io.ConnectionEndpoint;
46  import org.apache.hc.client5.http.io.HttpClientConnectionManager;
47  import org.apache.hc.client5.http.io.HttpClientConnectionOperator;
48  import org.apache.hc.client5.http.io.LeaseRequest;
49  import org.apache.hc.client5.http.io.ManagedHttpClientConnection;
50  import org.apache.hc.client5.http.socket.ConnectionSocketFactory;
51  import org.apache.hc.client5.http.socket.PlainConnectionSocketFactory;
52  import org.apache.hc.client5.http.ssl.SSLConnectionSocketFactory;
53  import org.apache.hc.core5.annotation.Contract;
54  import org.apache.hc.core5.annotation.Internal;
55  import org.apache.hc.core5.annotation.ThreadingBehavior;
56  import org.apache.hc.core5.function.Resolver;
57  import org.apache.hc.core5.http.ClassicHttpRequest;
58  import org.apache.hc.core5.http.ClassicHttpResponse;
59  import org.apache.hc.core5.http.HttpException;
60  import org.apache.hc.core5.http.HttpHost;
61  import org.apache.hc.core5.http.URIScheme;
62  import org.apache.hc.core5.http.config.Registry;
63  import org.apache.hc.core5.http.config.RegistryBuilder;
64  import org.apache.hc.core5.http.impl.io.HttpRequestExecutor;
65  import org.apache.hc.core5.http.io.HttpConnectionFactory;
66  import org.apache.hc.core5.http.io.SocketConfig;
67  import org.apache.hc.core5.http.protocol.HttpContext;
68  import org.apache.hc.core5.io.CloseMode;
69  import org.apache.hc.core5.pool.ConnPoolControl;
70  import org.apache.hc.core5.pool.LaxConnPool;
71  import org.apache.hc.core5.pool.ManagedConnPool;
72  import org.apache.hc.core5.pool.PoolConcurrencyPolicy;
73  import org.apache.hc.core5.pool.PoolEntry;
74  import org.apache.hc.core5.pool.PoolReusePolicy;
75  import org.apache.hc.core5.pool.PoolStats;
76  import org.apache.hc.core5.pool.StrictConnPool;
77  import org.apache.hc.core5.util.Args;
78  import org.apache.hc.core5.util.Asserts;
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(RegistryBuilder.<ConnectionSocketFactory>create()
124                 .register(URIScheme.HTTP.id, PlainConnectionSocketFactory.getSocketFactory())
125                 .register(URIScheme.HTTPS.id, SSLConnectionSocketFactory.getSocketFactory())
126                 .build());
127     }
128 
129     public PoolingHttpClientConnectionManager(
130             final Registry<ConnectionSocketFactory> socketFactoryRegistry) {
131         this(socketFactoryRegistry, null);
132     }
133 
134     public PoolingHttpClientConnectionManager(
135             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
136             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
137         this(socketFactoryRegistry, PoolConcurrencyPolicy.STRICT, TimeValue.NEG_ONE_MILLISECOND, connFactory);
138     }
139 
140     public PoolingHttpClientConnectionManager(
141             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
142             final PoolConcurrencyPolicy poolConcurrencyPolicy,
143             final TimeValue timeToLive,
144             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
145         this(socketFactoryRegistry, poolConcurrencyPolicy, PoolReusePolicy.LIFO, timeToLive, connFactory);
146     }
147 
148     public PoolingHttpClientConnectionManager(
149             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
150             final PoolConcurrencyPolicy poolConcurrencyPolicy,
151             final PoolReusePolicy poolReusePolicy,
152             final TimeValue timeToLive) {
153         this(socketFactoryRegistry, poolConcurrencyPolicy, poolReusePolicy, timeToLive, null);
154     }
155 
156     public PoolingHttpClientConnectionManager(
157             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
158             final PoolConcurrencyPolicy poolConcurrencyPolicy,
159             final PoolReusePolicy poolReusePolicy,
160             final TimeValue timeToLive,
161             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
162         this(socketFactoryRegistry, poolConcurrencyPolicy, poolReusePolicy, timeToLive, null, null, connFactory);
163     }
164 
165     public PoolingHttpClientConnectionManager(
166             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
167             final PoolConcurrencyPolicy poolConcurrencyPolicy,
168             final PoolReusePolicy poolReusePolicy,
169             final TimeValue timeToLive,
170             final SchemePortResolver schemePortResolver,
171             final DnsResolver dnsResolver,
172             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
173         this(new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
174                 poolConcurrencyPolicy,
175                 poolReusePolicy,
176                 timeToLive,
177                 connFactory);
178     }
179 
180     @Internal
181     protected PoolingHttpClientConnectionManager(
182             final HttpClientConnectionOperator httpClientConnectionOperator,
183             final PoolConcurrencyPolicy poolConcurrencyPolicy,
184             final PoolReusePolicy poolReusePolicy,
185             final TimeValue timeToLive,
186             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
187         super();
188         this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
189         switch (poolConcurrencyPolicy != null ? poolConcurrencyPolicy : PoolConcurrencyPolicy.STRICT) {
190             case STRICT:
191                 this.pool = new StrictConnPool<HttpRoute, ManagedHttpClientConnection>(
192                         DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
193                         DEFAULT_MAX_TOTAL_CONNECTIONS,
194                         timeToLive,
195                         poolReusePolicy,
196                         null) {
197 
198                     @Override
199                     public void closeExpired() {
200                         enumAvailable(e -> closeIfExpired(e));
201                     }
202 
203                 };
204                 break;
205             case LAX:
206                 this.pool = new LaxConnPool<HttpRoute, ManagedHttpClientConnection>(
207                         DEFAULT_MAX_CONNECTIONS_PER_ROUTE,
208                         timeToLive,
209                         poolReusePolicy,
210                         null) {
211 
212                     @Override
213                     public void closeExpired() {
214                         enumAvailable(e -> closeIfExpired(e));
215                     }
216 
217                 };
218                 break;
219             default:
220                 throw new IllegalArgumentException("Unexpected PoolConcurrencyPolicy value: " + poolConcurrencyPolicy);
221         }
222         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
223         this.closed = new AtomicBoolean(false);
224     }
225 
226     @Internal
227     protected PoolingHttpClientConnectionManager(
228             final HttpClientConnectionOperator httpClientConnectionOperator,
229             final ManagedConnPool<HttpRoute, ManagedHttpClientConnection> pool,
230             final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
231         super();
232         this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
233         this.pool = Args.notNull(pool, "Connection pool");
234         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
235         this.closed = new AtomicBoolean(false);
236     }
237 
238     @Override
239     public void close() {
240         close(CloseMode.GRACEFUL);
241     }
242 
243     @Override
244     public void close(final CloseMode closeMode) {
245         if (this.closed.compareAndSet(false, true)) {
246             if (LOG.isDebugEnabled()) {
247                 LOG.debug("Shutdown connection pool {}", closeMode);
248             }
249             this.pool.close(closeMode);
250             LOG.debug("Connection pool shut down");
251         }
252     }
253 
254     private InternalConnectionEndpoint cast(final ConnectionEndpoint endpoint) {
255         if (endpoint instanceof InternalConnectionEndpoint) {
256             return (InternalConnectionEndpoint) endpoint;
257         }
258         throw new IllegalStateException("Unexpected endpoint class: " + endpoint.getClass());
259     }
260 
261     private SocketConfig resolveSocketConfig(final HttpRoute route) {
262         final Resolver<HttpRoute, SocketConfig> resolver = this.socketConfigResolver;
263         final SocketConfig socketConfig = resolver != null ? resolver.resolve(route) : null;
264         return socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
265     }
266 
267     private ConnectionConfig resolveConnectionConfig(final HttpRoute route) {
268         final Resolver<HttpRoute, ConnectionConfig> resolver = this.connectionConfigResolver;
269         final ConnectionConfig connectionConfig = resolver != null ? resolver.resolve(route) : null;
270         return connectionConfig != null ? connectionConfig : ConnectionConfig.DEFAULT;
271     }
272 
273     private TlsConfig resolveTlsConfig(final HttpHost host) {
274         final Resolver<HttpHost, TlsConfig> resolver = this.tlsConfigResolver;
275         final TlsConfig tlsConfig = resolver != null ? resolver.resolve(host) : null;
276         return tlsConfig != null ? tlsConfig : TlsConfig.DEFAULT;
277     }
278 
279     private TimeValue resolveValidateAfterInactivity(final ConnectionConfig connectionConfig) {
280         final TimeValue timeValue = connectionConfig.getValidateAfterInactivity();
281         return timeValue != null ? timeValue : TimeValue.ofSeconds(2);
282     }
283 
284     public LeaseRequest lease(final String id, final HttpRoute route, final Object state) {
285         return lease(id, route, Timeout.DISABLED, state);
286     }
287 
288     @Override
289     public LeaseRequest lease(
290             final String id,
291             final HttpRoute route,
292             final Timeout requestTimeout,
293             final Object state) {
294         Args.notNull(route, "HTTP route");
295         if (LOG.isDebugEnabled()) {
296             LOG.debug("{} endpoint lease request ({}) {}", id, requestTimeout, ConnPoolSupport.formatStats(route, state, pool));
297         }
298         final Future<PoolEntry<HttpRoute, ManagedHttpClientConnection>> leaseFuture = this.pool.lease(route, state, requestTimeout, null);
299         return new LeaseRequest() {
300 
301             private volatile ConnectionEndpoint endpoint;
302 
303             @Override
304             public synchronized ConnectionEndpoint get(
305                     final Timeout timeout) throws InterruptedException, ExecutionException, TimeoutException {
306                 Args.notNull(timeout, "Operation timeout");
307                 if (this.endpoint != null) {
308                     return this.endpoint;
309                 }
310                 final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry;
311                 try {
312                     poolEntry = leaseFuture.get(timeout.getDuration(), timeout.getTimeUnit());
313                 } catch (final TimeoutException ex) {
314                     leaseFuture.cancel(true);
315                     throw ex;
316                 }
317                 if (LOG.isDebugEnabled()) {
318                     LOG.debug("{} endpoint leased {}", id, ConnPoolSupport.formatStats(route, state, pool));
319                 }
320                 final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
321                 try {
322                     if (poolEntry.hasConnection()) {
323                         final TimeValue timeToLive = connectionConfig.getTimeToLive();
324                         if (TimeValue.isNonNegative(timeToLive)) {
325                             final Deadline deadline = Deadline.calculate(poolEntry.getCreated(), timeToLive);
326                             if (deadline.isExpired()) {
327                                 poolEntry.discardConnection(CloseMode.GRACEFUL);
328                             }
329                         }
330                     }
331                     if (poolEntry.hasConnection()) {
332                         final TimeValue timeValue = resolveValidateAfterInactivity(connectionConfig);
333                         if (TimeValue.isNonNegative(timeValue)) {
334                             final Deadline deadline = Deadline.calculate(poolEntry.getUpdated(), timeValue);
335                             if (deadline.isExpired()) {
336                                 final ManagedHttpClientConnection conn = poolEntry.getConnection();
337                                 boolean stale;
338                                 try {
339                                     stale = conn.isStale();
340                                 } catch (final IOException ignore) {
341                                     stale = true;
342                                 }
343                                 if (stale) {
344                                     if (LOG.isDebugEnabled()) {
345                                         LOG.debug("{} connection {} is stale", id, ConnPoolSupport.getId(conn));
346                                     }
347                                     poolEntry.discardConnection(CloseMode.IMMEDIATE);
348                                 }
349                             }
350                         }
351                     }
352                     final ManagedHttpClientConnection conn = poolEntry.getConnection();
353                     if (conn != null) {
354                         conn.activate();
355                     } else {
356                         poolEntry.assignConnection(connFactory.createConnection(null));
357                     }
358                     this.endpoint = new InternalConnectionEndpoint(poolEntry);
359                     if (LOG.isDebugEnabled()) {
360                         LOG.debug("{} acquired {}", id, ConnPoolSupport.getId(endpoint));
361                     }
362                     return this.endpoint;
363                 } catch (final Exception ex) {
364                     if (LOG.isDebugEnabled()) {
365                         LOG.debug("{} endpoint lease failed", id);
366                     }
367                     pool.release(poolEntry, false);
368                     throw new ExecutionException(ex.getMessage(), ex);
369                 }
370             }
371 
372             @Override
373             public boolean cancel() {
374                 return leaseFuture.cancel(true);
375             }
376 
377         };
378 
379     }
380 
381     @Override
382     public void release(final ConnectionEndpoint endpoint, final Object state, final TimeValue keepAlive) {
383         Args.notNull(endpoint, "Managed endpoint");
384         final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry = cast(endpoint).detach();
385         if (entry == null) {
386             return;
387         }
388         if (LOG.isDebugEnabled()) {
389             LOG.debug("{} releasing endpoint", ConnPoolSupport.getId(endpoint));
390         }
391         final ManagedHttpClientConnection conn = entry.getConnection();
392         if (conn != null && keepAlive == null) {
393             conn.close(CloseMode.GRACEFUL);
394         }
395         boolean reusable = conn != null && conn.isOpen() && conn.isConsistent();
396         try {
397             if (reusable) {
398                 entry.updateState(state);
399                 entry.updateExpiry(keepAlive);
400                 conn.passivate();
401                 if (LOG.isDebugEnabled()) {
402                     final String s;
403                     if (TimeValue.isPositive(keepAlive)) {
404                         s = "for " + keepAlive;
405                     } else {
406                         s = "indefinitely";
407                     }
408                     LOG.debug("{} connection {} can be kept alive {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn), s);
409                 }
410             } else {
411                 if (LOG.isDebugEnabled()) {
412                     LOG.debug("{} connection is not kept alive", ConnPoolSupport.getId(endpoint));
413                 }
414             }
415         } catch (final RuntimeException ex) {
416             reusable = false;
417             throw ex;
418         } finally {
419             this.pool.release(entry, reusable);
420             if (LOG.isDebugEnabled()) {
421                 LOG.debug("{} connection released {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.formatStats(entry.getRoute(), entry.getState(), pool));
422             }
423         }
424     }
425 
426     @Override
427     public void connect(final ConnectionEndpoint endpoint, final TimeValue timeout, final HttpContext context) throws IOException {
428         Args.notNull(endpoint, "Managed endpoint");
429         final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
430         if (internalEndpoint.isConnected()) {
431             return;
432         }
433         final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getPoolEntry();
434         if (!poolEntry.hasConnection()) {
435             poolEntry.assignConnection(connFactory.createConnection(null));
436         }
437         final HttpRoute route = poolEntry.getRoute();
438         final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost();
439         final SocketConfig socketConfig = resolveSocketConfig(route);
440         final ConnectionConfig connectionConfig = resolveConnectionConfig(route);
441         final TlsConfig tlsConfig = resolveTlsConfig(host);
442         final Timeout connectTimeout = timeout != null ? Timeout.of(timeout.getDuration(), timeout.getTimeUnit()) : connectionConfig.getConnectTimeout();
443         if (LOG.isDebugEnabled()) {
444             LOG.debug("{} connecting endpoint to {} ({})", ConnPoolSupport.getId(endpoint), host, connectTimeout);
445         }
446         final ManagedHttpClientConnection conn = poolEntry.getConnection();
447         this.connectionOperator.connect(
448                 conn,
449                 host,
450                 route.getLocalSocketAddress(),
451                 connectTimeout,
452                 socketConfig,
453                 tlsConfig,
454                 context);
455         if (LOG.isDebugEnabled()) {
456             LOG.debug("{} connected {}", ConnPoolSupport.getId(endpoint), ConnPoolSupport.getId(conn));
457         }
458         final Timeout socketTimeout = connectionConfig.getSocketTimeout();
459         if (socketTimeout != null) {
460             conn.setSocketTimeout(socketTimeout);
461         }
462     }
463 
464     @Override
465     public void upgrade(final ConnectionEndpoint endpoint, final HttpContext context) throws IOException {
466         Args.notNull(endpoint, "Managed endpoint");
467         final InternalConnectionEndpoint internalEndpoint = cast(endpoint);
468         final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = internalEndpoint.getValidatedPoolEntry();
469         final HttpRoute route = poolEntry.getRoute();
470         final HttpHost host = route.getProxyHost() != null ? route.getProxyHost() : route.getTargetHost();
471         final TlsConfig tlsConfig = resolveTlsConfig(host);
472         this.connectionOperator.upgrade(poolEntry.getConnection(), route.getTargetHost(), tlsConfig, context);
473     }
474 
475     @Override
476     public void closeIdle(final TimeValue idleTime) {
477         Args.notNull(idleTime, "Idle time");
478         if (LOG.isDebugEnabled()) {
479             LOG.debug("Closing connections idle longer than {}", idleTime);
480         }
481         this.pool.closeIdle(idleTime);
482     }
483 
484     @Override
485     public void closeExpired() {
486         LOG.debug("Closing expired connections");
487         this.pool.closeExpired();
488     }
489 
490     @Override
491     public Set<HttpRoute> getRoutes() {
492         return this.pool.getRoutes();
493     }
494 
495     @Override
496     public int getMaxTotal() {
497         return this.pool.getMaxTotal();
498     }
499 
500     @Override
501     public void setMaxTotal(final int max) {
502         this.pool.setMaxTotal(max);
503     }
504 
505     @Override
506     public int getDefaultMaxPerRoute() {
507         return this.pool.getDefaultMaxPerRoute();
508     }
509 
510     @Override
511     public void setDefaultMaxPerRoute(final int max) {
512         this.pool.setDefaultMaxPerRoute(max);
513     }
514 
515     @Override
516     public int getMaxPerRoute(final HttpRoute route) {
517         return this.pool.getMaxPerRoute(route);
518     }
519 
520     @Override
521     public void setMaxPerRoute(final HttpRoute route, final int max) {
522         this.pool.setMaxPerRoute(route, max);
523     }
524 
525     @Override
526     public PoolStats getTotalStats() {
527         return this.pool.getTotalStats();
528     }
529 
530     @Override
531     public PoolStats getStats(final HttpRoute route) {
532         return this.pool.getStats(route);
533     }
534 
535     /**
536      * Sets the same {@link SocketConfig} for all routes
537      */
538     public void setDefaultSocketConfig(final SocketConfig config) {
539         this.socketConfigResolver = (route) -> config;
540     }
541 
542     /**
543      * Sets {@link Resolver} of {@link SocketConfig} on a per route basis.
544      *
545      * @since 5.2
546      */
547     public void setSocketConfigResolver(final Resolver<HttpRoute, SocketConfig> socketConfigResolver) {
548         this.socketConfigResolver = socketConfigResolver;
549     }
550 
551     /**
552      * Sets the same {@link ConnectionConfig} for all routes
553      *
554      * @since 5.2
555      */
556     public void setDefaultConnectionConfig(final ConnectionConfig config) {
557         this.connectionConfigResolver = (route) -> config;
558     }
559 
560     /**
561      * Sets {@link Resolver} of {@link ConnectionConfig} on a per route basis.
562      *
563      * @since 5.2
564      */
565     public void setConnectionConfigResolver(final Resolver<HttpRoute, ConnectionConfig> connectionConfigResolver) {
566         this.connectionConfigResolver = connectionConfigResolver;
567     }
568 
569     /**
570      * Sets the same {@link ConnectionConfig} for all hosts
571      *
572      * @since 5.2
573      */
574     public void setDefaultTlsConfig(final TlsConfig config) {
575         this.tlsConfigResolver = (host) -> config;
576     }
577 
578     /**
579      * Sets {@link Resolver} of {@link TlsConfig} on a per host basis.
580      *
581      * @since 5.2
582      */
583     public void setTlsConfigResolver(final Resolver<HttpHost, TlsConfig> tlsConfigResolver) {
584         this.tlsConfigResolver = tlsConfigResolver;
585     }
586 
587     void closeIfExpired(final PoolEntry<HttpRoute, ManagedHttpClientConnection> entry) {
588         final long now = System.currentTimeMillis();
589         if (entry.getExpiryDeadline().isBefore(now)) {
590             entry.discardConnection(CloseMode.GRACEFUL);
591         } else {
592             final ConnectionConfig connectionConfig = resolveConnectionConfig(entry.getRoute());
593             final TimeValue timeToLive = connectionConfig.getTimeToLive();
594             if (timeToLive != null && Deadline.calculate(entry.getCreated(), timeToLive).isBefore(now)) {
595                 entry.discardConnection(CloseMode.GRACEFUL);
596             }
597         }
598     }
599 
600     /**
601      * @deprecated Use custom {@link #setConnectionConfigResolver(Resolver)}
602      */
603     @Deprecated
604     public SocketConfig getDefaultSocketConfig() {
605         return SocketConfig.DEFAULT;
606     }
607 
608     /**
609      * @since 4.4
610      *
611      * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}.
612      */
613     @Deprecated
614     public TimeValue getValidateAfterInactivity() {
615         return ConnectionConfig.DEFAULT.getValidateAfterInactivity();
616     }
617 
618     /**
619      * Defines period of inactivity after which persistent connections must
620      * be re-validated prior to being {@link #lease(String, HttpRoute, Object)} leased} to the consumer.
621      * Negative values passed to this method disable connection validation. This check helps
622      * detect connections that have become stale (half-closed) while kept inactive in the pool.
623      *
624      * @since 4.4
625      *
626      * @deprecated Use {@link #setConnectionConfigResolver(Resolver)}.
627      */
628     @Deprecated
629     public void setValidateAfterInactivity(final TimeValue validateAfterInactivity) {
630         setDefaultConnectionConfig(ConnectionConfig.custom()
631                 .setValidateAfterInactivity(validateAfterInactivity)
632                 .build());
633     }
634 
635     private static final PrefixedIncrementingId INCREMENTING_ID = new PrefixedIncrementingId("ep-");
636 
637     class InternalConnectionEndpoint extends ConnectionEndpoint implements Identifiable {
638 
639         private final AtomicReference<PoolEntry<HttpRoute, ManagedHttpClientConnection>> poolEntryRef;
640         private final String id;
641 
642         InternalConnectionEndpoint(
643                 final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry) {
644             this.poolEntryRef = new AtomicReference<>(poolEntry);
645             this.id = INCREMENTING_ID.getNextId();
646         }
647 
648         @Override
649         public String getId() {
650             return id;
651         }
652 
653         PoolEntry<HttpRoute, ManagedHttpClientConnection> getPoolEntry() {
654             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
655             if (poolEntry == null) {
656                 throw new ConnectionShutdownException();
657             }
658             return poolEntry;
659         }
660 
661         PoolEntry<HttpRoute, ManagedHttpClientConnection> getValidatedPoolEntry() {
662             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = getPoolEntry();
663             final ManagedHttpClientConnection connection = poolEntry.getConnection();
664             Asserts.check(connection != null && connection.isOpen(), "Endpoint is not connected");
665             return poolEntry;
666         }
667 
668         PoolEntry<HttpRoute, ManagedHttpClientConnection> detach() {
669             return poolEntryRef.getAndSet(null);
670         }
671 
672         @Override
673         public void close(final CloseMode closeMode) {
674             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
675             if (poolEntry != null) {
676                 poolEntry.discardConnection(closeMode);
677             }
678         }
679 
680         @Override
681         public void close() throws IOException {
682             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = poolEntryRef.get();
683             if (poolEntry != null) {
684                 poolEntry.discardConnection(CloseMode.GRACEFUL);
685             }
686         }
687 
688         @Override
689         public boolean isConnected() {
690             final PoolEntry<HttpRoute, ManagedHttpClientConnection> poolEntry = getPoolEntry();
691             final ManagedHttpClientConnection connection = poolEntry.getConnection();
692             return connection != null && connection.isOpen();
693         }
694 
695         @Override
696         public void setSocketTimeout(final Timeout timeout) {
697             getValidatedPoolEntry().getConnection().setSocketTimeout(timeout);
698         }
699 
700         @Override
701         public ClassicHttpResponse execute(
702                 final String exchangeId,
703                 final ClassicHttpRequest request,
704                 final HttpRequestExecutor requestExecutor,
705                 final HttpContext context) throws IOException, HttpException {
706             Args.notNull(request, "HTTP request");
707             Args.notNull(requestExecutor, "Request executor");
708             final ManagedHttpClientConnection connection = getValidatedPoolEntry().getConnection();
709             if (LOG.isDebugEnabled()) {
710                 LOG.debug("{} executing exchange {} over {}", id, exchangeId, ConnPoolSupport.getId(connection));
711             }
712             return requestExecutor.execute(request, connection, context);
713         }
714 
715     }
716 }