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.GuardedBy;
42  import org.apache.http.annotation.ThreadSafe;
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.SchemePortResolver;
54  import org.apache.http.conn.ManagedHttpClientConnection;
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  @ThreadSafe
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      @GuardedBy("this")
91      private ManagedHttpClientConnection conn;
92  
93      @GuardedBy("this")
94      private HttpRoute route;
95  
96      @GuardedBy("this")
97      private Object state;
98  
99      @GuardedBy("this")
100     private long updated;
101 
102     @GuardedBy("this")
103     private long expiry;
104 
105     @GuardedBy("this")
106     private boolean leased;
107 
108     @GuardedBy("this")
109     private SocketConfig socketConfig;
110 
111     @GuardedBy("this")
112     private ConnectionConfig connConfig;
113 
114     private final AtomicBoolean isShutdown;
115 
116     private static Registry<ConnectionSocketFactory> getDefaultRegistry() {
117         return RegistryBuilder.<ConnectionSocketFactory>create()
118                 .register("http", PlainConnectionSocketFactory.getSocketFactory())
119                 .register("https", SSLConnectionSocketFactory.getSocketFactory())
120                 .build();
121     }
122 
123     public BasicHttpClientConnectionManager(
124         final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
125         final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory,
126         final SchemePortResolver schemePortResolver,
127         final DnsResolver dnsResolver) {
128       this(
129           new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver),
130           connFactory
131       );
132     }
133 
134     /**
135      * @since 4.4
136      */
137     public BasicHttpClientConnectionManager(
138             final HttpClientConnectionOperator httpClientConnectionOperator,
139             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
140         super();
141         this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator");
142         this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE;
143         this.expiry = Long.MAX_VALUE;
144         this.socketConfig = SocketConfig.DEFAULT;
145         this.connConfig = ConnectionConfig.DEFAULT;
146         this.isShutdown = new AtomicBoolean(false);
147     }
148 
149     public BasicHttpClientConnectionManager(
150             final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
151             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
152         this(socketFactoryRegistry, connFactory, null, null);
153     }
154 
155     public BasicHttpClientConnectionManager(
156             final Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
157         this(socketFactoryRegistry, null, null, null);
158     }
159 
160     public BasicHttpClientConnectionManager() {
161         this(getDefaultRegistry(), null, null, null);
162     }
163 
164     @Override
165     protected void finalize() throws Throwable {
166         try {
167             shutdown();
168         } finally { // Make sure we call overridden method even if shutdown barfs
169             super.finalize();
170         }
171     }
172 
173     @Override
174     public void close() {
175         shutdown();
176     }
177 
178     HttpRoute getRoute() {
179         return route;
180     }
181 
182     Object getState() {
183         return state;
184     }
185 
186     public synchronized SocketConfig getSocketConfig() {
187         return socketConfig;
188     }
189 
190     public synchronized void setSocketConfig(final SocketConfig socketConfig) {
191         this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
192     }
193 
194     public synchronized ConnectionConfig getConnectionConfig() {
195         return connConfig;
196     }
197 
198     public synchronized void setConnectionConfig(final ConnectionConfig connConfig) {
199         this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT;
200     }
201 
202     @Override
203     public final ConnectionRequest requestConnection(
204             final HttpRoute route,
205             final Object state) {
206         Args.notNull(route, "Route");
207         return new ConnectionRequest() {
208 
209             @Override
210             public boolean cancel() {
211                 // Nothing to abort, since requests are immediate.
212                 return false;
213             }
214 
215             @Override
216             public HttpClientConnection get(final long timeout, final TimeUnit tunit) {
217                 return BasicHttpClientConnectionManager.this.getConnection(
218                         route, state);
219             }
220 
221         };
222     }
223 
224     private void closeConnection() {
225         if (this.conn != null) {
226             this.log.debug("Closing connection");
227             try {
228                 this.conn.close();
229             } catch (final IOException iox) {
230                 if (this.log.isDebugEnabled()) {
231                     this.log.debug("I/O exception closing connection", iox);
232                 }
233             }
234             this.conn = null;
235         }
236     }
237 
238     private void shutdownConnection() {
239         if (this.conn != null) {
240             this.log.debug("Shutting down connection");
241             try {
242                 this.conn.shutdown();
243             } catch (final IOException iox) {
244                 if (this.log.isDebugEnabled()) {
245                     this.log.debug("I/O exception shutting down connection", iox);
246                 }
247             }
248             this.conn = null;
249         }
250     }
251 
252     private void checkExpiry() {
253         if (this.conn != null && System.currentTimeMillis() >= this.expiry) {
254             if (this.log.isDebugEnabled()) {
255                 this.log.debug("Connection expired @ " + new Date(this.expiry));
256             }
257             closeConnection();
258         }
259     }
260 
261     synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) {
262         Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down");
263         if (this.log.isDebugEnabled()) {
264             this.log.debug("Get connection for route " + route);
265         }
266         Asserts.check(!this.leased, "Connection is still allocated");
267         if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) {
268             closeConnection();
269         }
270         this.route = route;
271         this.state = state;
272         checkExpiry();
273         if (this.conn == null) {
274             this.conn = this.connFactory.create(route, this.connConfig);
275         }
276         this.leased = true;
277         return this.conn;
278     }
279 
280     @Override
281     public synchronized void releaseConnection(
282             final HttpClientConnection conn,
283             final Object state,
284             final long keepalive, final TimeUnit tunit) {
285         Args.notNull(conn, "Connection");
286         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
287         if (this.log.isDebugEnabled()) {
288             this.log.debug("Releasing connection " + conn);
289         }
290         if (this.isShutdown.get()) {
291             return;
292         }
293         try {
294             this.updated = System.currentTimeMillis();
295             if (!this.conn.isOpen()) {
296                 this.conn = null;
297                 this.route = null;
298                 this.conn = null;
299                 this.expiry = Long.MAX_VALUE;
300             } else {
301                 this.state = state;
302                 if (this.log.isDebugEnabled()) {
303                     final String s;
304                     if (keepalive > 0) {
305                         s = "for " + keepalive + " " + tunit;
306                     } else {
307                         s = "indefinitely";
308                     }
309                     this.log.debug("Connection can be kept alive " + s);
310                 }
311                 if (keepalive > 0) {
312                     this.expiry = this.updated + tunit.toMillis(keepalive);
313                 } else {
314                     this.expiry = Long.MAX_VALUE;
315                 }
316             }
317         } finally {
318             this.leased = false;
319         }
320     }
321 
322     @Override
323     public void connect(
324             final HttpClientConnection conn,
325             final HttpRoute route,
326             final int connectTimeout,
327             final HttpContext context) throws IOException {
328         Args.notNull(conn, "Connection");
329         Args.notNull(route, "HTTP route");
330         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
331         final HttpHost host;
332         if (route.getProxyHost() != null) {
333             host = route.getProxyHost();
334         } else {
335             host = route.getTargetHost();
336         }
337         final InetSocketAddress localAddress = route.getLocalSocketAddress();
338         this.connectionOperator.connect(this.conn, host, localAddress,
339                 connectTimeout, this.socketConfig, context);
340     }
341 
342     @Override
343     public void upgrade(
344             final HttpClientConnection conn,
345             final HttpRoute route,
346             final HttpContext context) throws IOException {
347         Args.notNull(conn, "Connection");
348         Args.notNull(route, "HTTP route");
349         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
350         this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context);
351     }
352 
353     @Override
354     public void routeComplete(
355             final HttpClientConnection conn,
356             final HttpRoute route,
357             final HttpContext context) throws IOException {
358     }
359 
360     @Override
361     public synchronized void closeExpiredConnections() {
362         if (this.isShutdown.get()) {
363             return;
364         }
365         if (!this.leased) {
366             checkExpiry();
367         }
368     }
369 
370     @Override
371     public synchronized void closeIdleConnections(final long idletime, final TimeUnit tunit) {
372         Args.notNull(tunit, "Time unit");
373         if (this.isShutdown.get()) {
374             return;
375         }
376         if (!this.leased) {
377             long time = tunit.toMillis(idletime);
378             if (time < 0) {
379                 time = 0;
380             }
381             final long deadline = System.currentTimeMillis() - time;
382             if (this.updated <= deadline) {
383                 closeConnection();
384             }
385         }
386     }
387 
388     @Override
389     public synchronized void shutdown() {
390         if (this.isShutdown.compareAndSet(false, true)) {
391             shutdownConnection();
392         }
393     }
394 
395 }