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.http.impl.conn;
28  
29  import java.io.Closeable;
30  import java.io.IOException;
31  import java.net.InetSocketAddress;
32  import java.util.Map;
33  import java.util.concurrent.ConcurrentHashMap;
34  import java.util.concurrent.ExecutionException;
35  import java.util.concurrent.Future;
36  import java.util.concurrent.TimeUnit;
37  import java.util.concurrent.TimeoutException;
38  
39  import org.apache.commons.logging.Log;
40  import org.apache.commons.logging.LogFactory;
41  import org.apache.http.HttpClientConnection;
42  import org.apache.http.HttpHost;
43  import org.apache.http.annotation.ThreadSafe;
44  import org.apache.http.config.ConnectionConfig;
45  import org.apache.http.config.Lookup;
46  import org.apache.http.config.Registry;
47  import org.apache.http.config.RegistryBuilder;
48  import org.apache.http.config.SocketConfig;
49  import org.apache.http.conn.ConnectionPoolTimeoutException;
50  import org.apache.http.conn.ConnectionRequest;
51  import org.apache.http.conn.DnsResolver;
52  import org.apache.http.conn.HttpClientConnectionManager;
53  import org.apache.http.conn.HttpConnectionFactory;
54  import org.apache.http.conn.SchemePortResolver;
55  import org.apache.http.conn.ManagedHttpClientConnection;
56  import org.apache.http.conn.routing.HttpRoute;
57  import org.apache.http.conn.socket.ConnectionSocketFactory;
58  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
59  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
60  import org.apache.http.pool.ConnFactory;
61  import org.apache.http.pool.ConnPoolControl;
62  import org.apache.http.pool.PoolStats;
63  import org.apache.http.protocol.HttpContext;
64  import org.apache.http.util.Args;
65  import org.apache.http.util.Asserts;
66  
67  /**
68   * <tt>ClientConnectionPoolManager</tt> maintains a pool of
69   * {@link HttpClientConnection}s and is able to service connection requests
70   * from multiple execution threads. Connections are pooled on a per route
71   * basis. A request for a route which already the manager has persistent
72   * connections for available in the pool will be services by leasing
73   * a connection from the pool rather than creating a brand new connection.
74   * <p/>
75   * <tt>ClientConnectionPoolManager</tt> maintains a maximum limit of connection
76   * on a per route basis and in total. Per default this implementation will
77   * create no more than than 2 concurrent connections per given route
78   * and no more 20 connections in total. For many real-world applications
79   * these limits may prove too constraining, especially if they use HTTP
80   * as a transport protocol for their services. Connection limits, however,
81   * can be adjusted using {@link ConnPoolControl} methods.
82   *
83   * @since 4.3
84   */
85  @ThreadSafe
86  public class PoolingHttpClientConnectionManager
87      implements HttpClientConnectionManager, ConnPoolControl<HttpRoute>, Closeable {
88  
89      private final Log log = LogFactory.getLog(getClass());
90  
91      private final ConfigData configData;
92      private final CPool pool;
93      private final HttpClientConnectionOperator connectionOperator;
94  
95      private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
96          return RegistryBuilder.<ConnectionSocketFactory>create()
97                  .register("http", PlainConnectionSocketFactory.getSocketFactory())
98                  .register("https", SSLConnectionSocketFactory.getSocketFactory())
99                  .build();
100     }
101 
102     public PoolingHttpClientConnectionManager() {
103         this(getDefaultRegistry());
104     }
105 
106     public PoolingHttpClientConnectionManager(final long timeToLive, final TimeUnit tunit) {
107         this(getDefaultRegistry(), null, null ,null, timeToLive, tunit);
108     }
109 
110     public PoolingHttpClientConnectionManager(
111             final Registry<ConnectionSocketFactory> socketFactoryRegistry) {
112         this(socketFactoryRegistry, null, null);
113     }
114 
115     public PoolingHttpClientConnectionManager(
116             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
117             final DnsResolver dnsResolver) {
118         this(socketFactoryRegistry, null, dnsResolver);
119     }
120 
121     public PoolingHttpClientConnectionManager(
122             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
123             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
124         this(socketFactoryRegistry, connFactory, null);
125     }
126 
127     public PoolingHttpClientConnectionManager(
128             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
129         this(getDefaultRegistry(), connFactory, null);
130     }
131 
132     public PoolingHttpClientConnectionManager(
133             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
134             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
135             final DnsResolver dnsResolver) {
136         this(socketFactoryRegistry, connFactory, null, dnsResolver, -1, TimeUnit.MILLISECONDS);
137     }
138 
139     public PoolingHttpClientConnectionManager(
140             final Registry<ConnectionSocketFactory> socketFactoryRegistry,
141             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
142             final SchemePortResolver schemePortResolver,
143             final DnsResolver dnsResolver,
144             final long timeToLive, final TimeUnit tunit) {
145         super();
146         this.configData = new ConfigData();
147         this.pool = new CPool(
148                 new InternalConnectionFactory(this.configData, connFactory), 2, 20, timeToLive, tunit);
149         this.connectionOperator = new HttpClientConnectionOperator(
150                 socketFactoryRegistry, schemePortResolver, dnsResolver);
151     }
152 
153     PoolingHttpClientConnectionManager(
154             final CPool pool,
155             final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
156             final SchemePortResolver schemePortResolver,
157             final DnsResolver dnsResolver) {
158         super();
159         this.configData = new ConfigData();
160         this.pool = pool;
161         this.connectionOperator = new HttpClientConnectionOperator(
162                 socketFactoryRegistry, schemePortResolver, dnsResolver);
163     }
164 
165     @Override
166     protected void finalize() throws Throwable {
167         try {
168             shutdown();
169         } finally {
170             super.finalize();
171         }
172     }
173 
174     public void close() {
175         shutdown();
176     }
177 
178     private String format(final HttpRoute route, final Object state) {
179         final StringBuilder buf = new StringBuilder();
180         buf.append("[route: ").append(route).append("]");
181         if (state != null) {
182             buf.append("[state: ").append(state).append("]");
183         }
184         return buf.toString();
185     }
186 
187     private String formatStats(final HttpRoute route) {
188         final StringBuilder buf = new StringBuilder();
189         final PoolStats totals = this.pool.getTotalStats();
190         final PoolStats stats = this.pool.getStats(route);
191         buf.append("[total kept alive: ").append(totals.getAvailable()).append("; ");
192         buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable());
193         buf.append(" of ").append(stats.getMax()).append("; ");
194         buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
195         buf.append(" of ").append(totals.getMax()).append("]");
196         return buf.toString();
197     }
198 
199     private String format(final CPoolEntry entry) {
200         final StringBuilder buf = new StringBuilder();
201         buf.append("[id: ").append(entry.getId()).append("]");
202         buf.append("[route: ").append(entry.getRoute()).append("]");
203         final Object state = entry.getState();
204         if (state != null) {
205             buf.append("[state: ").append(state).append("]");
206         }
207         return buf.toString();
208     }
209 
210     public ConnectionRequest requestConnection(
211             final HttpRoute route,
212             final Object state) {
213         Args.notNull(route, "HTTP route");
214         if (this.log.isDebugEnabled()) {
215             this.log.debug("Connection request: " + format(route, state) + formatStats(route));
216         }
217         final Future<CPoolEntry> future = this.pool.lease(route, state, null);
218         return new ConnectionRequest() {
219 
220             public boolean cancel() {
221                 return future.cancel(true);
222             }
223 
224             public HttpClientConnection get(
225                     final long timeout,
226                     final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
227                 return leaseConnection(future, timeout, tunit);
228             }
229 
230         };
231 
232     }
233 
234     protected HttpClientConnection leaseConnection(
235             final Future<CPoolEntry> future,
236             final long timeout,
237             final TimeUnit tunit) throws InterruptedException, ExecutionException, ConnectionPoolTimeoutException {
238         final CPoolEntry entry;
239         try {
240             entry = future.get(timeout, tunit);
241             if (entry == null || future.isCancelled()) {
242                 throw new InterruptedException();
243             }
244             Asserts.check(entry.getConnection() != null, "Pool entry with no connection");
245             if (this.log.isDebugEnabled()) {
246                 this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
247             }
248             return CPoolProxy.newProxy(entry);
249         } catch (final TimeoutException ex) {
250             throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
251         }
252     }
253 
254     public void releaseConnection(
255             final HttpClientConnection managedConn,
256             final Object state,
257             final long keepalive, final TimeUnit tunit) {
258         Args.notNull(managedConn, "Managed connection");
259         synchronized (managedConn) {
260             final CPoolEntry entry = CPoolProxy.detach(managedConn);
261             if (entry == null) {
262                 return;
263             }
264             final ManagedHttpClientConnection conn = entry.getConnection();
265             try {
266                 if (conn.isOpen()) {
267                     entry.setState(state);
268                     entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
269                     if (this.log.isDebugEnabled()) {
270                         final String s;
271                         if (keepalive > 0) {
272                             s = "for " + (double) keepalive / 1000 + " seconds";
273                         } else {
274                             s = "indefinitely";
275                         }
276                         this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
277                     }
278                 }
279             } finally {
280                 this.pool.release(entry, conn.isOpen() && entry.isRouteComplete());
281                 if (this.log.isDebugEnabled()) {
282                     this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute()));
283                 }
284             }
285         }
286     }
287 
288     public void connect(
289             final HttpClientConnection managedConn,
290             final HttpRoute route,
291             final int connectTimeout,
292             final HttpContext context) throws IOException {
293         Args.notNull(managedConn, "Managed Connection");
294         Args.notNull(route, "HTTP route");
295         final ManagedHttpClientConnection conn;
296         synchronized (managedConn) {
297             final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);
298             conn = entry.getConnection();
299         }
300         final HttpHost host;
301         if (route.getProxyHost() != null) {
302             host = route.getProxyHost();
303         } else {
304             host = route.getTargetHost();
305         }
306         final InetSocketAddress localAddress = route.getLocalSocketAddress();
307         SocketConfig socketConfig = this.configData.getSocketConfig(host);
308         if (socketConfig == null) {
309             socketConfig = this.configData.getDefaultSocketConfig();
310         }
311         if (socketConfig == null) {
312             socketConfig = SocketConfig.DEFAULT;
313         }
314         this.connectionOperator.connect(
315                 conn, host, localAddress, connectTimeout, socketConfig, context);
316     }
317 
318     public void upgrade(
319             final HttpClientConnection managedConn,
320             final HttpRoute route,
321             final HttpContext context) throws IOException {
322         Args.notNull(managedConn, "Managed Connection");
323         Args.notNull(route, "HTTP route");
324         final ManagedHttpClientConnection conn;
325         synchronized (managedConn) {
326             final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);
327             conn = entry.getConnection();
328         }
329         this.connectionOperator.upgrade(conn, route.getTargetHost(), context);
330     }
331 
332     public void routeComplete(
333             final HttpClientConnection managedConn,
334             final HttpRoute route,
335             final HttpContext context) throws IOException {
336         Args.notNull(managedConn, "Managed Connection");
337         Args.notNull(route, "HTTP route");
338         synchronized (managedConn) {
339             final CPoolEntry entry = CPoolProxy.getPoolEntry(managedConn);
340             entry.markRouteComplete();
341         }
342     }
343 
344     public void shutdown() {
345         this.log.debug("Connection manager is shutting down");
346         try {
347             this.pool.shutdown();
348         } catch (final IOException ex) {
349             this.log.debug("I/O exception shutting down connection manager", ex);
350         }
351         this.log.debug("Connection manager shut down");
352     }
353 
354     public void closeIdleConnections(final long idleTimeout, final TimeUnit tunit) {
355         if (this.log.isDebugEnabled()) {
356             this.log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit);
357         }
358         this.pool.closeIdle(idleTimeout, tunit);
359     }
360 
361     public void closeExpiredConnections() {
362         this.log.debug("Closing expired connections");
363         this.pool.closeExpired();
364     }
365 
366     public int getMaxTotal() {
367         return this.pool.getMaxTotal();
368     }
369 
370     public void setMaxTotal(final int max) {
371         this.pool.setMaxTotal(max);
372     }
373 
374     public int getDefaultMaxPerRoute() {
375         return this.pool.getDefaultMaxPerRoute();
376     }
377 
378     public void setDefaultMaxPerRoute(final int max) {
379         this.pool.setDefaultMaxPerRoute(max);
380     }
381 
382     public int getMaxPerRoute(final HttpRoute route) {
383         return this.pool.getMaxPerRoute(route);
384     }
385 
386     public void setMaxPerRoute(final HttpRoute route, final int max) {
387         this.pool.setMaxPerRoute(route, max);
388     }
389 
390     public PoolStats getTotalStats() {
391         return this.pool.getTotalStats();
392     }
393 
394     public PoolStats getStats(final HttpRoute route) {
395         return this.pool.getStats(route);
396     }
397 
398     public SocketConfig getDefaultSocketConfig() {
399         return this.configData.getDefaultSocketConfig();
400     }
401 
402     public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) {
403         this.configData.setDefaultSocketConfig(defaultSocketConfig);
404     }
405 
406     public ConnectionConfig getDefaultConnectionConfig() {
407         return this.configData.getDefaultConnectionConfig();
408     }
409 
410     public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig) {
411         this.configData.setDefaultConnectionConfig(defaultConnectionConfig);
412     }
413 
414     public SocketConfig getSocketConfig(final HttpHost host) {
415         return this.configData.getSocketConfig(host);
416     }
417 
418     public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig) {
419         this.configData.setSocketConfig(host, socketConfig);
420     }
421 
422     public ConnectionConfig getConnectionConfig(final HttpHost host) {
423         return this.configData.getConnectionConfig(host);
424     }
425 
426     public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig) {
427         this.configData.setConnectionConfig(host, connectionConfig);
428     }
429 
430     static class ConfigData {
431 
432         private final Map<HttpHost, SocketConfig> socketConfigMap;
433         private final Map<HttpHost, ConnectionConfig> connectionConfigMap;
434         private volatile SocketConfig defaultSocketConfig;
435         private volatile ConnectionConfig defaultConnectionConfig;
436 
437         ConfigData() {
438             super();
439             this.socketConfigMap = new ConcurrentHashMap<HttpHost, SocketConfig>();
440             this.connectionConfigMap = new ConcurrentHashMap<HttpHost, ConnectionConfig>();
441         }
442 
443         public SocketConfig getDefaultSocketConfig() {
444             return this.defaultSocketConfig;
445         }
446 
447         public void setDefaultSocketConfig(final SocketConfig defaultSocketConfig) {
448             this.defaultSocketConfig = defaultSocketConfig;
449         }
450 
451         public ConnectionConfig getDefaultConnectionConfig() {
452             return this.defaultConnectionConfig;
453         }
454 
455         public void setDefaultConnectionConfig(final ConnectionConfig defaultConnectionConfig) {
456             this.defaultConnectionConfig = defaultConnectionConfig;
457         }
458 
459         public SocketConfig getSocketConfig(final HttpHost host) {
460             return this.socketConfigMap.get(host);
461         }
462 
463         public void setSocketConfig(final HttpHost host, final SocketConfig socketConfig) {
464             this.socketConfigMap.put(host, socketConfig);
465         }
466 
467         public ConnectionConfig getConnectionConfig(final HttpHost host) {
468             return this.connectionConfigMap.get(host);
469         }
470 
471         public void setConnectionConfig(final HttpHost host, final ConnectionConfig connectionConfig) {
472             this.connectionConfigMap.put(host, connectionConfig);
473         }
474 
475     }
476 
477     static class InternalConnectionFactory implements ConnFactory<HttpRoute, ManagedHttpClientConnection> {
478 
479         private final ConfigData configData;
480         private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
481 
482         InternalConnectionFactory(
483                 final ConfigData configData,
484                 final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
485             super();
486             this.configData = configData != null ? configData : new ConfigData();
487             this.connFactory = connFactory != null ? connFactory :
488                 ManagedHttpClientConnectionFactory.INSTANCE;
489         }
490 
491         public ManagedHttpClientConnection create(final HttpRoute route) throws IOException {
492             ConnectionConfig config = null;
493             if (route.getProxyHost() != null) {
494                 config = this.configData.getConnectionConfig(route.getProxyHost());
495             }
496             if (config == null) {
497                 config = this.configData.getConnectionConfig(route.getTargetHost());
498             }
499             if (config == null) {
500                 config = this.configData.getDefaultConnectionConfig();
501             }
502             if (config == null) {
503                 config = ConnectionConfig.DEFAULT;
504             }
505             return this.connFactory.create(route, config);
506         }
507 
508     }
509 
510 }