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