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