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.OperatedClientConnection;
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 OperatedClientConnection client connections} and
54   * 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  @ThreadSafe
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 tunit) {
102         this(schemeRegistry, timeToLive, tunit, new SystemDefaultDnsResolver());
103     }
104 
105     public PoolingClientConnectionManager(final SchemeRegistry schemeRegistry,
106                 final long timeToLive, final TimeUnit tunit,
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, tunit);
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     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     public ClientConnectionRequest requestConnection(
179             final HttpRoute route,
180             final Object state) {
181         Args.notNull(route, "HTTP route");
182         if (this.log.isDebugEnabled()) {
183             this.log.debug("Connection request: " + format(route, state) + formatStats(route));
184         }
185         final Future<HttpPoolEntry> future = this.pool.lease(route, state);
186 
187         return new ClientConnectionRequest() {
188 
189             public void abortRequest() {
190                 future.cancel(true);
191             }
192 
193             public ManagedClientConnection getConnection(
194                     final long timeout,
195                     final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
196                 return leaseConnection(future, timeout, tunit);
197             }
198 
199         };
200 
201     }
202 
203     ManagedClientConnection leaseConnection(
204             final Future<HttpPoolEntry> future,
205             final long timeout,
206             final TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException {
207         HttpPoolEntry entry;
208         try {
209             entry = future.get(timeout, tunit);
210             if (entry == null || future.isCancelled()) {
211                 throw new InterruptedException();
212             }
213             Asserts.check(entry.getConnection() != null, "Pool entry with no connection");
214             if (this.log.isDebugEnabled()) {
215                 this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
216             }
217             return new ManagedClientConnectionImpl(this, this.operator, entry);
218         } catch (final ExecutionException ex) {
219             Throwable cause = ex.getCause();
220             if (cause == null) {
221                 cause = ex;
222             }
223             this.log.error("Unexpected exception leasing connection from pool", cause);
224             // Should never happen
225             throw new InterruptedException();
226         } catch (final TimeoutException ex) {
227             throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
228         }
229     }
230 
231     public void releaseConnection(
232             final ManagedClientConnection conn, final long keepalive, final TimeUnit tunit) {
233 
234         Args.check(conn instanceof ManagedClientConnectionImpl, "Connection class mismatch, " +
235             "connection not obtained from this manager");
236         final ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn;
237         Asserts.check(managedConn.getManager() == this, "Connection not obtained from this manager");
238         synchronized (managedConn) {
239             final HttpPoolEntry entry = managedConn.detach();
240             if (entry == null) {
241                 return;
242             }
243             try {
244                 if (managedConn.isOpen() && !managedConn.isMarkedReusable()) {
245                     try {
246                         managedConn.shutdown();
247                     } catch (final IOException iox) {
248                         if (this.log.isDebugEnabled()) {
249                             this.log.debug("I/O exception shutting down released connection", iox);
250                         }
251                     }
252                 }
253                 // Only reusable connections can be kept alive
254                 if (managedConn.isMarkedReusable()) {
255                     entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
256                     if (this.log.isDebugEnabled()) {
257                         String s;
258                         if (keepalive > 0) {
259                             s = "for " + keepalive + " " + tunit;
260                         } else {
261                             s = "indefinitely";
262                         }
263                         this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
264                     }
265                 }
266             } finally {
267                 this.pool.release(entry, managedConn.isMarkedReusable());
268             }
269             if (this.log.isDebugEnabled()) {
270                 this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute()));
271             }
272         }
273     }
274 
275     public void shutdown() {
276         this.log.debug("Connection manager is shutting down");
277         try {
278             this.pool.shutdown();
279         } catch (final IOException ex) {
280             this.log.debug("I/O exception shutting down connection manager", ex);
281         }
282         this.log.debug("Connection manager shut down");
283     }
284 
285     public void closeIdleConnections(final long idleTimeout, final TimeUnit tunit) {
286         if (this.log.isDebugEnabled()) {
287             this.log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit);
288         }
289         this.pool.closeIdle(idleTimeout, tunit);
290     }
291 
292     public void closeExpiredConnections() {
293         this.log.debug("Closing expired connections");
294         this.pool.closeExpired();
295     }
296 
297     public int getMaxTotal() {
298         return this.pool.getMaxTotal();
299     }
300 
301     public void setMaxTotal(final int max) {
302         this.pool.setMaxTotal(max);
303     }
304 
305     public int getDefaultMaxPerRoute() {
306         return this.pool.getDefaultMaxPerRoute();
307     }
308 
309     public void setDefaultMaxPerRoute(final int max) {
310         this.pool.setDefaultMaxPerRoute(max);
311     }
312 
313     public int getMaxPerRoute(final HttpRoute route) {
314         return this.pool.getMaxPerRoute(route);
315     }
316 
317     public void setMaxPerRoute(final HttpRoute route, final int max) {
318         this.pool.setMaxPerRoute(route, max);
319     }
320 
321     public PoolStats getTotalStats() {
322         return this.pool.getTotalStats();
323     }
324 
325     public PoolStats getStats(final HttpRoute route) {
326         return this.pool.getStats(route);
327     }
328 
329 }
330