View Javadoc

1   /*
2    * ====================================================================
3    *
4    *  Licensed to the Apache Software Foundation (ASF) under one or more
5    *  contributor license agreements.  See the NOTICE file distributed with
6    *  this work for additional information regarding copyright ownership.
7    *  The ASF licenses this file to You under the Apache License, Version 2.0
8    *  (the "License"); you may not use this file except in compliance with
9    *  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, software
14   *  distributed under the License is distributed on an "AS IS" BASIS,
15   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   *  See the License for the specific language governing permissions and
17   *  limitations under the License.
18   * ====================================================================
19   *
20   * This software consists of voluntary contributions made by many
21   * individuals on behalf of the Apache Software Foundation.  For more
22   * information on the Apache Software Foundation, please see
23   * <http://www.apache.org/>.
24   *
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.routing.HttpRoute;
39  import org.apache.http.conn.scheme.SchemeRegistry;
40  import org.apache.http.conn.ClientConnectionManager;
41  import org.apache.http.conn.ClientConnectionOperator;
42  import org.apache.http.conn.ClientConnectionRequest;
43  import org.apache.http.conn.ConnectionPoolTimeoutException;
44  import org.apache.http.conn.ManagedClientConnection;
45  import org.apache.http.conn.OperatedClientConnection;
46  import org.apache.http.pool.ConnPoolControl;
47  import org.apache.http.pool.PoolStats;
48  import org.apache.http.impl.conn.DefaultClientConnectionOperator;
49  import org.apache.http.impl.conn.SchemeRegistryFactory;
50  import org.apache.http.conn.DnsResolver;
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  @ThreadSafe
71  public class PoolingClientConnectionManager implements ClientConnectionManager, ConnPoolControl<HttpRoute> {
72  
73      private final Log log = LogFactory.getLog(getClass());
74  
75      private final SchemeRegistry schemeRegistry;
76  
77      private final HttpConnPool pool;
78  
79      private final ClientConnectionOperator operator;
80  
81      /** the custom-configured DNS lookup mechanism. */
82      private final DnsResolver dnsResolver;
83  
84      public PoolingClientConnectionManager(final SchemeRegistry schreg) {
85          this(schreg, -1, TimeUnit.MILLISECONDS);
86      }
87  
88      public PoolingClientConnectionManager(final SchemeRegistry schreg,final DnsResolver dnsResolver) {
89          this(schreg, -1, TimeUnit.MILLISECONDS,dnsResolver);
90      }
91  
92      public PoolingClientConnectionManager() {
93          this(SchemeRegistryFactory.createDefault());
94      }
95  
96      public PoolingClientConnectionManager(
97              final SchemeRegistry schemeRegistry,
98              final long timeToLive, final TimeUnit tunit) {
99          this(schemeRegistry, timeToLive, tunit, new SystemDefaultDnsResolver());
100     }
101 
102     public PoolingClientConnectionManager(final SchemeRegistry schemeRegistry,
103                 final long timeToLive, final TimeUnit tunit,
104                 final DnsResolver dnsResolver) {
105         super();
106         if (schemeRegistry == null) {
107             throw new IllegalArgumentException("Scheme registry may not be null");
108         }
109         if (dnsResolver == null) {
110             throw new IllegalArgumentException("DNS resolver may not be null");
111         }
112         this.schemeRegistry = schemeRegistry;
113         this.dnsResolver  = dnsResolver;
114         this.operator = createConnectionOperator(schemeRegistry);
115         this.pool = new HttpConnPool(this.log, this.operator, 2, 20, timeToLive, tunit);
116     }
117 
118     @Override
119     protected void finalize() throws Throwable {
120         try {
121             shutdown();
122         } finally {
123             super.finalize();
124         }
125     }
126 
127     /**
128      * Hook for creating the connection operator.
129      * It is called by the constructor.
130      * Derived classes can override this method to change the
131      * instantiation of the operator.
132      * The default implementation here instantiates
133      * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}.
134      *
135      * @param schreg    the scheme registry.
136      *
137      * @return  the connection operator to use
138      */
139     protected ClientConnectionOperator createConnectionOperator(SchemeRegistry schreg) {
140             return new DefaultClientConnectionOperator(schreg, this.dnsResolver);
141     }
142 
143     public SchemeRegistry getSchemeRegistry() {
144         return this.schemeRegistry;
145     }
146 
147     private String format(final HttpRoute route, final Object state) {
148         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         StringBuilder buf = new StringBuilder();
158         PoolStats totals = this.pool.getTotalStats();
159         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         StringBuilder buf = new StringBuilder();
170         buf.append("[id: ").append(entry.getId()).append("]");
171         buf.append("[route: ").append(entry.getRoute()).append("]");
172         Object state = entry.getState();
173         if (state != null) {
174             buf.append("[state: ").append(state).append("]");
175         }
176         return buf.toString();
177     }
178 
179     public ClientConnectionRequest requestConnection(
180             final HttpRoute route,
181             final Object state) {
182         if (route == null) {
183             throw new IllegalArgumentException("HTTP route may not be null");
184         }
185         if (this.log.isDebugEnabled()) {
186             this.log.debug("Connection request: " + format(route, state) + formatStats(route));
187         }
188         final Future<HttpPoolEntry> future = this.pool.lease(route, state);
189 
190         return new ClientConnectionRequest() {
191 
192             public void abortRequest() {
193                 future.cancel(true);
194             }
195 
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         HttpPoolEntry entry;
211         try {
212             entry = future.get(timeout, tunit);
213             if (entry == null || future.isCancelled()) {
214                 throw new InterruptedException();
215             }
216             if (entry.getConnection() == null) {
217                 throw new IllegalStateException("Pool entry with no connection");
218             }
219             if (this.log.isDebugEnabled()) {
220                 this.log.debug("Connection leased: " + format(entry) + formatStats(entry.getRoute()));
221             }
222             return new ManagedClientConnectionImpl(this, this.operator, entry);
223         } catch (ExecutionException ex) {
224             Throwable cause = ex.getCause();
225             if (cause == null) {
226                 cause = ex;
227             }
228             this.log.error("Unexpected exception leasing connection from pool", cause);
229             // Should never happen
230             throw new InterruptedException();
231         } catch (TimeoutException ex) {
232             throw new ConnectionPoolTimeoutException("Timeout waiting for connection from pool");
233         }
234     }
235 
236     public void releaseConnection(
237             final ManagedClientConnection conn, final long keepalive, final TimeUnit tunit) {
238 
239         if (!(conn instanceof ManagedClientConnectionImpl)) {
240             throw new IllegalArgumentException
241                 ("Connection class mismatch, " +
242                  "connection not obtained from this manager.");
243         }
244         ManagedClientConnectionImpl managedConn = (ManagedClientConnectionImpl) conn;
245         if (managedConn.getManager() != this) {
246             throw new IllegalStateException("Connection not obtained from this manager.");
247         }
248 
249         synchronized (managedConn) {
250             HttpPoolEntry entry = managedConn.detach();
251             if (entry == null) {
252                 return;
253             }
254             try {
255                 if (managedConn.isOpen() && !managedConn.isMarkedReusable()) {
256                     try {
257                         managedConn.shutdown();
258                     } catch (IOException iox) {
259                         if (this.log.isDebugEnabled()) {
260                             this.log.debug("I/O exception shutting down released connection", iox);
261                         }
262                     }
263                 }
264                 // Only reusable connections can be kept alive
265                 if (managedConn.isMarkedReusable()) {
266                     entry.updateExpiry(keepalive, tunit != null ? tunit : TimeUnit.MILLISECONDS);
267                     if (this.log.isDebugEnabled()) {
268                         String s;
269                         if (keepalive > 0) {
270                             s = "for " + keepalive + " " + tunit;
271                         } else {
272                             s = "indefinitely";
273                         }
274                         this.log.debug("Connection " + format(entry) + " can be kept alive " + s);
275                     }
276                 }
277             } finally {
278                 this.pool.release(entry, managedConn.isMarkedReusable());
279             }
280             if (this.log.isDebugEnabled()) {
281                 this.log.debug("Connection released: " + format(entry) + formatStats(entry.getRoute()));
282             }
283         }
284     }
285 
286     public void shutdown() {
287         this.log.debug("Connection manager is shutting down");
288         try {
289             this.pool.shutdown();
290         } catch (IOException ex) {
291             this.log.debug("I/O exception shutting down connection manager", ex);
292         }
293         this.log.debug("Connection manager shut down");
294     }
295 
296     public void closeIdleConnections(long idleTimeout, TimeUnit tunit) {
297         if (this.log.isDebugEnabled()) {
298             this.log.debug("Closing connections idle longer than " + idleTimeout + " " + tunit);
299         }
300         this.pool.closeIdle(idleTimeout, tunit);
301     }
302 
303     public void closeExpiredConnections() {
304         this.log.debug("Closing expired connections");
305         this.pool.closeExpired();
306     }
307 
308     public int getMaxTotal() {
309         return this.pool.getMaxTotal();
310     }
311 
312     public void setMaxTotal(int max) {
313         this.pool.setMaxTotal(max);
314     }
315 
316     public int getDefaultMaxPerRoute() {
317         return this.pool.getDefaultMaxPerRoute();
318     }
319 
320     public void setDefaultMaxPerRoute(int max) {
321         this.pool.setDefaultMaxPerRoute(max);
322     }
323 
324     public int getMaxPerRoute(final HttpRoute route) {
325         return this.pool.getMaxPerRoute(route);
326     }
327 
328     public void setMaxPerRoute(final HttpRoute route, int max) {
329         this.pool.setMaxPerRoute(route, max);
330     }
331 
332     public PoolStats getTotalStats() {
333         return this.pool.getTotalStats();
334     }
335 
336     public PoolStats getStats(final HttpRoute route) {
337         return this.pool.getStats(route);
338     }
339 
340 }
341