1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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.PlainSocketFactory;
59 import org.apache.http.conn.ssl.SSLSocketFactory;
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
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", PlainSocketFactory.getSocketFactory())
98 .register("https", SSLSocketFactory.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<ManagedHttpClientConnection> connFactory) {
124 this(socketFactoryRegistry, connFactory, null);
125 }
126
127 public PoolingHttpClientConnectionManager(
128 final HttpConnectionFactory<ManagedHttpClientConnection> connFactory) {
129 this(getDefaultRegistry(), connFactory, null);
130 }
131
132 public PoolingHttpClientConnectionManager(
133 final Registry<ConnectionSocketFactory> socketFactoryRegistry,
134 final HttpConnectionFactory<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<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 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 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 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 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<ManagedHttpClientConnection> connFactory;
481
482 InternalConnectionFactory(
483 final ConfigData configData,
484 final HttpConnectionFactory<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(config);
506 }
507
508 }
509
510 }