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