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.IOException;
30  import java.util.concurrent.ExecutionException;
31  import java.util.concurrent.Future;
32  import java.util.concurrent.TimeUnit;
33  import java.util.concurrent.TimeoutException;
34  
35  import org.apache.commons.logging.Log;
36  import org.apache.commons.logging.LogFactory;
37  import org.apache.http.annotation.Contract;
38  import org.apache.http.annotation.ThreadingBehavior;
39  import org.apache.http.conn.ClientConnectionManager;
40  import org.apache.http.conn.ClientConnectionOperator;
41  import org.apache.http.conn.ClientConnectionRequest;
42  import org.apache.http.conn.ConnectionPoolTimeoutException;
43  import org.apache.http.conn.DnsResolver;
44  import org.apache.http.conn.ManagedClientConnection;
45  import org.apache.http.conn.routing.HttpRoute;
46  import org.apache.http.conn.scheme.SchemeRegistry;
47  import org.apache.http.pool.ConnPoolControl;
48  import org.apache.http.pool.PoolStats;
49  import org.apache.http.util.Args;
50  import org.apache.http.util.Asserts;
51  
52  /**
53   * Manages a pool of {@link org.apache.http.conn.OperatedClientConnection}
54   * and is able to service connection requests from multiple execution threads.
55   * Connections are pooled on a per route basis. A request for a route which
56   * already the manager has persistent connections for available in the pool
57   * will be services by leasing a connection from the pool rather than
58   * creating a brand new connection.
59   * <p>
60   * PoolingConnectionManager maintains a maximum limit of connection on
61   * a per route basis and in total. Per default this implementation will
62   * create no more than than 2 concurrent connections per given route
63   * and no more 20 connections in total. For many real-world applications
64   * these limits may prove too constraining, especially if they use HTTP
65   * as a transport protocol for their services. Connection limits, however,
66   * can be adjusted using HTTP parameters.
67   *
68   * @since 4.2
69   *
70   * @deprecated (4.3) use {@link PoolingHttpClientConnectionManager}.
71   */
72  @Deprecated
73  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
74  public class PoolingClientConnectionManager implements ClientConnectionManager, ConnPoolControl<HttpRoute> {
75  
76      private final Log log = LogFactory.getLog(getClass());
77  
78      private final SchemeRegistry schemeRegistry;
79  
80      private final HttpConnPool pool;
81  
82      private final ClientConnectionOperator operator;
83  
84      /** the custom-configured DNS lookup mechanism. */
85      private final DnsResolver dnsResolver;
86  
87      public PoolingClientConnectionManager(final SchemeRegistry schreg) {
88          this(schreg, -1, TimeUnit.MILLISECONDS);
89      }
90  
91      public PoolingClientConnectionManager(final SchemeRegistry schreg,final DnsResolver dnsResolver) {
92          this(schreg, -1, TimeUnit.MILLISECONDS,dnsResolver);
93      }
94  
95      public PoolingClientConnectionManager() {
96          this(SchemeRegistryFactory.createDefault());
97      }
98  
99      public PoolingClientConnectionManager(
100             final SchemeRegistry schemeRegistry,
101             final long timeToLive, final TimeUnit timeUnit) {
102         this(schemeRegistry, timeToLive, timeUnit, new SystemDefaultDnsResolver());
103     }
104 
105     public PoolingClientConnectionManager(final SchemeRegistry schemeRegistry,
106                 final long timeToLive, final TimeUnit timeUnit,
107                 final DnsResolver dnsResolver) {
108         super();
109         Args.notNull(schemeRegistry, "Scheme registry");
110         Args.notNull(dnsResolver, "DNS resolver");
111         this.schemeRegistry = schemeRegistry;
112         this.dnsResolver  = dnsResolver;
113         this.operator = createConnectionOperator(schemeRegistry);
114         this.pool = new HttpConnPool(this.log, this.operator, 2, 20, timeToLive, timeUnit);
115     }
116 
117     @Override
118     protected void finalize() throws Throwable {
119         try {
120             shutdown();
121         } finally {
122             super.finalize();
123         }
124     }
125 
126     /**
127      * Hook for creating the connection operator.
128      * It is called by the constructor.
129      * Derived classes can override this method to change the
130      * instantiation of the operator.
131      * The default implementation here instantiates
132      * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}.
133      *
134      * @param schreg    the scheme registry.
135      *
136      * @return  the connection operator to use
137      */
138     protected ClientConnectionOperator createConnectionOperator(final SchemeRegistry schreg) {
139             return new DefaultClientConnectionOperator(schreg, this.dnsResolver);
140     }
141 
142     @Override
143     public SchemeRegistry getSchemeRegistry() {
144         return this.schemeRegistry;
145     }
146 
147     private String format(final HttpRoute route, final Object state) {
148         final StringBuilder buf = new StringBuilder();
149         buf.append("[route: ").append(route).append("]");
150         if (state != null) {
151             buf.append("[state: ").append(state).append("]");
152         }
153         return buf.toString();
154     }
155 
156     private String formatStats(final HttpRoute route) {
157         final StringBuilder buf = new StringBuilder();
158         final PoolStats totals = this.pool.getTotalStats();
159         final PoolStats stats = this.pool.getStats(route);
160         buf.append("[total kept alive: ").append(totals.getAvailable()).append("; ");
161         buf.append("route allocated: ").append(stats.getLeased() + stats.getAvailable());
162         buf.append(" of ").append(stats.getMax()).append("; ");
163         buf.append("total allocated: ").append(totals.getLeased() + totals.getAvailable());
164         buf.append(" of ").append(totals.getMax()).append("]");
165         return buf.toString();
166     }
167 
168     private String format(final HttpPoolEntry entry) {
169         final StringBuilder buf = new StringBuilder();
170         buf.append("[id: ").append(entry.getId()).append("]");
171         buf.append("[route: ").append(entry.getRoute()).append("]");
172         final Object state = entry.getState();
173         if (state != null) {
174             buf.append("[state: ").append(state).append("]");
175         }
176         return buf.toString();
177     }
178 
179     @Override
180     public ClientConnectionRequest requestConnection(
181             final HttpRoute route,
182             final Object state) {
183         Args.notNull(route, "HTTP route");
184         if (this.log.isDebugEnabled()) {
185             this.log.debug("Connection request: " + format(route, state) + formatStats(route));
186         }
187         final Future<HttpPoolEntry> future = this.pool.lease(route, state);
188 
189         return new ClientConnectionRequest() {
190 
191             @Override
192             public void abortRequest() {
193                 future.cancel(true);
194             }
195 
196             @Override
197             public ManagedClientConnection getConnection(
198                     final long timeout,
199                     final TimeUnit timeUnit) throws InterruptedException, ConnectionPoolTimeoutException {
200                 return leaseConnection(future, timeout, timeUnit);
201             }
202 
203         };
204 
205     }
206 
207     ManagedClientConnection leaseConnection(
208             final Future<HttpPoolEntry> future,
209             final long timeout,
210             final TimeUnit timeUnit) throws InterruptedException, ConnectionPoolTimeoutException {
211         final HttpPoolEntry entry;
212         try {
213             entry = future.get(timeout, timeUnit);
214             if (entry == null || future.isCancelled()) {
215                 throw new InterruptedException();
216             }
217             Asserts.check(entry.getConnection() != null, "Pool entry with no connection");
218             if (this.log.isDebugEnabled()) {
219                 this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
220             }
221             return new ManagedClientConnectionImpl(this, this.operator, entry);
222         } catch (final ExecutionException ex) {
223             Throwable cause = ex.getCause();
224             if (cause == null) {
225                 cause = ex;
226             }
227             this.log.error("Unexpected exception leasing connection from pool", cause);
228             // Should never happen
229             throw new InterruptedException();
230         } catch (final TimeoutException ex) {
231             throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
232         }
233     }
234 
235     @Override
236     public void releaseConnection(
237             final ManagedClientConnection conn, final long keepalive, final TimeUnit timeUnit) {
238 
239         Args.check(conn instanceof ManagedClientConnectionImpl, "Connection class mismatch, " +
240             "connection not obtained from this manager");
241         final ManagedClientConnectionImplhttp/impl/conn/ManagedClientConnectionImpl.html#ManagedClientConnectionImpl">ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn;
242         Asserts.check(managedConn.getManager() == this, "Connection not obtained from this manager");
243         synchronized (managedConn) {
244             final HttpPoolEntry entry = managedConn.detach();
245             if (entry == null) {
246                 return;
247             }
248             try {
249                 if (managedConn.isOpen() && !managedConn.isMarkedReusable()) {
250                     try {
251                         managedConn.shutdown();
252                     } catch (final IOException iox) {
253                         if (this.log.isDebugEnabled()) {
254                             this.log.debug("I/O exception shutting down released connection", iox);
255                         }
256                     }
257                 }
258                 // Only reusable connections can be kept alive
259                 if (managedConn.isMarkedReusable()) {
260                     entry.updateExpiry(keepalive, timeUnit != null ? timeUnit : TimeUnit.MILLISECONDS);
261                     if (this.log.isDebugEnabled()) {
262                         final String s;
263                         if (keepalive > 0) {
264                             s = "for " + keepalive + " " + timeUnit;
265                         } else {
266                             s = "indefinitely";
267                         }
268                         this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
269                     }
270                 }
271             } finally {
272                 this.pool.release(entry, managedConn.isMarkedReusable());
273             }
274             if (this.log.isDebugEnabled()) {
275                 this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute()));
276             }
277         }
278     }
279 
280     @Override
281     public void shutdown() {
282         this.log.debug("Connection manager is shutting down");
283         try {
284             this.pool.shutdown();
285         } catch (final IOException ex) {
286             this.log.debug("I/O exception shutting down connection manager", ex);
287         }
288         this.log.debug("Connection manager shut down");
289     }
290 
291     @Override
292     public void closeIdleConnections(final long idleTimeout, final TimeUnit timeUnit) {
293         if (this.log.isDebugEnabled()) {
294             this.log.debug("Closing connections idle longer than " + idleTimeout + " " + timeUnit);
295         }
296         this.pool.closeIdle(idleTimeout, timeUnit);
297     }
298 
299     @Override
300     public void closeExpiredConnections() {
301         this.log.debug("Closing expired connections");
302         this.pool.closeExpired();
303     }
304 
305     @Override
306     public int getMaxTotal() {
307         return this.pool.getMaxTotal();
308     }
309 
310     @Override
311     public void setMaxTotal(final int max) {
312         this.pool.setMaxTotal(max);
313     }
314 
315     @Override
316     public int getDefaultMaxPerRoute() {
317         return this.pool.getDefaultMaxPerRoute();
318     }
319 
320     @Override
321     public void setDefaultMaxPerRoute(final int max) {
322         this.pool.setDefaultMaxPerRoute(max);
323     }
324 
325     @Override
326     public int getMaxPerRoute(final HttpRoute route) {
327         return this.pool.getMaxPerRoute(route);
328     }
329 
330     @Override
331     public void setMaxPerRoute(final HttpRoute route, final int max) {
332         this.pool.setMaxPerRoute(route, max);
333     }
334 
335     @Override
336     public PoolStats getTotalStats() {
337         return this.pool.getTotalStats();
338     }
339 
340     @Override
341     public PoolStats getStats(final HttpRoute route) {
342         return this.pool.getStats(route);
343     }
344 
345 }
346