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  
28  package org.apache.http.impl.conn;
29  
30  import java.io.Closeable;
31  import java.io.IOException;
32  import java.net.InetSocketAddress;
33  import java.util.Date;
34  import java.util.concurrent.TimeUnit;
35  import java.util.concurrent.atomic.AtomicBoolean;
36  
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.apache.http.HttpClientConnection;
40  import org.apache.http.HttpHost;
41  import org.apache.http.annotation.Contract;
42  import org.apache.http.annotation.ThreadingBehavior;
43  import org.apache.http.config.ConnectionConfig;
44  import org.apache.http.config.Lookup;
45  import org.apache.http.config.Registry;
46  import org.apache.http.config.RegistryBuilder;
47  import org.apache.http.config.SocketConfig;
48  import org.apache.http.conn.ConnectionRequest;
49  import org.apache.http.conn.DnsResolver;
50  import org.apache.http.conn.HttpClientConnectionManager;
51  import org.apache.http.conn.HttpClientConnectionOperator;
52  import org.apache.http.conn.HttpConnectionFactory;
53  import org.apache.http.conn.ManagedHttpClientConnection;
54  import org.apache.http.conn.SchemePortResolver;
55  import org.apache.http.conn.routing.HttpRoute;
56  import org.apache.http.conn.socket.ConnectionSocketFactory;
57  import org.apache.http.conn.socket.PlainConnectionSocketFactory;
58  import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
59  import org.apache.http.protocol.HttpContext;
60  import org.apache.http.util.Args;
61  import org.apache.http.util.Asserts;
62  import org.apache.http.util.LangUtils;
63  
64  /**
65   * A connection manager for a single connection. This connection manager maintains only one active
66   * connection. Even though this class is fully thread-safe it ought to be used by one execution
67   * thread only, as only one thread a time can lease the connection at a time.
68   * <p>
69   * This connection manager will make an effort to reuse the connection for subsequent requests
70   * with the same {@link HttpRoute route}. It will, however, close the existing connection and
71   * open it for the given route, if the route of the persistent connection does not match that
72   * of the connection request. If the connection has been already been allocated
73   * {@link IllegalStateException} is thrown.
74   * </p>
75   * <p>
76   * This connection manager implementation should be used inside an EJB container instead of
77   * {@link PoolingHttpClientConnectionManager}.
78   * </p>
79   *
80   * @since 4.3
81   */
82  @Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL)
83  public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable {
84  
85      private final Log log = LogFactory.getLog(getClass());
86  
87      private final HttpClientConnectionOperator connectionOperator;
88      private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
89  
90      private ManagedHttpClientConnection conn;
91      private HttpRoute route;
92      private Object state;
93      private long updated;
94      private long expiry;
95      private boolean leased;
96      private SocketConfig socketConfig;
97      private ConnectionConfig connConfig;
98  
99      private final AtomicBoolean isShutdown;
100 
101     private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
102         return RegistryBuilder.<ConnectionSocketFactory>create()
103                 .register("http", PlainConnectionSocketFactory.getSocketFactory())
104                 .register("https", SSLConnectionSocketFactory.getSocketFactory())
105                 .build();
106     }
107 
108     public BasicHttpClientConnectionManager(
109         final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
110         final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
111         final SchemePortResolver schemePortResolver,
112         final DnsResolver dnsResolver) {
113       this(
114           new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
115           connFactory
116       );
117     }
118 
119     /**
120      * @since 4.4
121      */
122     public BasicHttpClientConnectionManager(
123             final HttpClientConnectionOperator httpClientConnectionOperator,
124             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
125         super();
126         this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
127         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
128         this.expiry = Long.MAX_VALUE;
129         this.socketConfig = SocketConfig.DEFAULT;
130         this.connConfig = ConnectionConfig.DEFAULT;
131         this.isShutdown = new AtomicBoolean(false);
132     }
133 
134     public BasicHttpClientConnectionManager(
135             final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
136             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
137         this(socketFactoryRegistry, connFactory, null, null);
138     }
139 
140     public BasicHttpClientConnectionManager(
141             final Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
142         this(socketFactoryRegistry, null, null, null);
143     }
144 
145     public BasicHttpClientConnectionManager() {
146         this(getDefaultRegistry(), null, null, null);
147     }
148 
149     @Override
150     protected void finalize() throws Throwable {
151         try {
152             shutdown();
153         } finally { // Make sure we call overridden method even if shutdown barfs
154             super.finalize();
155         }
156     }
157 
158     @Override
159     public void close() {
160         if (this.isShutdown.compareAndSet(false, true)) {
161             closeConnection();
162         }
163     }
164 
165     HttpRoute getRoute() {
166         return route;
167     }
168 
169     Object getState() {
170         return state;
171     }
172 
173     public synchronized SocketConfig getSocketConfig() {
174         return socketConfig;
175     }
176 
177     public synchronized void setSocketConfig(final SocketConfig socketConfig) {
178         this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
179     }
180 
181     public synchronized ConnectionConfig getConnectionConfig() {
182         return connConfig;
183     }
184 
185     public synchronized void setConnectionConfig(final ConnectionConfig connConfig) {
186         this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT;
187     }
188 
189     @Override
190     public final ConnectionRequest requestConnection(
191             final HttpRoute route,
192             final Object state) {
193         Args.notNull(route, "Route");
194         return new ConnectionRequest() {
195 
196             @Override
197             public boolean cancel() {
198                 // Nothing to abort, since requests are immediate.
199                 return false;
200             }
201 
202             @Override
203             public HttpClientConnection get(final long timeout, final TimeUnit timeUnit) {
204                 return BasicHttpClientConnectionManager.this.getConnection(
205                         route, state);
206             }
207 
208         };
209     }
210 
211     private synchronized void closeConnection() {
212         if (this.conn != null) {
213             this.log.debug("Closing connection");
214             try {
215                 this.conn.close();
216             } catch (final IOException iox) {
217                 if (this.log.isDebugEnabled()) {
218                     this.log.debug("I/O exception closing connection", iox);
219                 }
220             }
221             this.conn = null;
222         }
223     }
224 
225     private void checkExpiry() {
226         if (this.conn != null && System.currentTimeMillis() >= this.expiry) {
227             if (this.log.isDebugEnabled()) {
228                 this.log.debug("Connection expired @ " + new Date(this.expiry));
229             }
230             closeConnection();
231         }
232     }
233 
234     synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) {
235         Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down");
236         if (this.log.isDebugEnabled()) {
237             this.log.debug("Get connection for route " + route);
238         }
239         Asserts.check(!this.leased, "Connection is still allocated");
240         if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) {
241             closeConnection();
242         }
243         this.route = route;
244         this.state = state;
245         checkExpiry();
246         if (this.conn == null) {
247             this.conn = this.connFactory.create(route, this.connConfig);
248         }
249         this.conn.setSocketTimeout(this.socketConfig.getSoTimeout());
250         this.leased = true;
251         return this.conn;
252     }
253 
254     @Override
255     public synchronized void releaseConnection(
256             final HttpClientConnection conn,
257             final Object state,
258             final long keepalive, final TimeUnit timeUnit) {
259         Args.notNull(conn, "Connection");
260         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
261         if (this.log.isDebugEnabled()) {
262             this.log.debug("Releasing connection " + conn);
263         }
264         if (this.isShutdown.get()) {
265             return;
266         }
267         try {
268             this.updated = System.currentTimeMillis();
269             if (!this.conn.isOpen()) {
270                 this.conn = null;
271                 this.route = null;
272                 this.conn = null;
273                 this.expiry = Long.MAX_VALUE;
274             } else {
275                 this.state = state;
276                 this.conn.setSocketTimeout(0);
277                 if (this.log.isDebugEnabled()) {
278                     final String s;
279                     if (keepalive > 0) {
280                         s = "for " + keepalive + " " + timeUnit;
281                     } else {
282                         s = "indefinitely";
283                     }
284                     this.log.debug("Connection can be kept alive " + s);
285                 }
286                 if (keepalive > 0) {
287                     this.expiry = this.updated + timeUnit.toMillis(keepalive);
288                 } else {
289                     this.expiry = Long.MAX_VALUE;
290                 }
291             }
292         } finally {
293             this.leased = false;
294         }
295     }
296 
297     @Override
298     public void connect(
299             final HttpClientConnection conn,
300             final HttpRoute route,
301             final int connectTimeout,
302             final HttpContext context) throws IOException {
303         Args.notNull(conn, "Connection");
304         Args.notNull(route, "HTTP route");
305         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
306         final HttpHost host;
307         if (route.getProxyHost() != null) {
308             host = route.getProxyHost();
309         } else {
310             host = route.getTargetHost();
311         }
312         final InetSocketAddress localAddress = route.getLocalSocketAddress();
313         this.connectionOperator.connect(this.conn, host, localAddress,
314                 connectTimeout, this.socketConfig, context);
315     }
316 
317     @Override
318     public void upgrade(
319             final HttpClientConnection conn,
320             final HttpRoute route,
321             final HttpContext context) throws IOException {
322         Args.notNull(conn, "Connection");
323         Args.notNull(route, "HTTP route");
324         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
325         this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context);
326     }
327 
328     @Override
329     public void routeComplete(
330             final HttpClientConnection conn,
331             final HttpRoute route,
332             final HttpContext context) throws IOException {
333     }
334 
335     @Override
336     public synchronized void closeExpiredConnections() {
337         if (this.isShutdown.get()) {
338             return;
339         }
340         if (!this.leased) {
341             checkExpiry();
342         }
343     }
344 
345     @Override
346     public synchronized void closeIdleConnections(final long idletime, final TimeUnit timeUnit) {
347         Args.notNull(timeUnit, "Time unit");
348         if (this.isShutdown.get()) {
349             return;
350         }
351         if (!this.leased) {
352             long time = timeUnit.toMillis(idletime);
353             if (time < 0) {
354                 time = 0;
355             }
356             final long deadline = System.currentTimeMillis() - time;
357             if (this.updated <= deadline) {
358                 closeConnection();
359             }
360         }
361     }
362 
363     @Override
364     public void shutdown() {
365         if (this.isShutdown.compareAndSet(false, true)) {
366             if (this.conn != null) {
367                 this.log.debug("Shutting down connection");
368                 try {
369                     this.conn.shutdown();
370                 } catch (final IOException iox) {
371                     if (this.log.isDebugEnabled()) {
372                         this.log.debug("I/O exception shutting down connection", iox);
373                     }
374                 }
375                 this.conn = null;
376             }
377         }
378     }
379 
380 }