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 ch.boye.httpclientandroidlib.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 ch.boye.httpclientandroidlib.androidextra.HttpClientAndroidLog;
38 /* LogFactory removed by HttpClient for Android script. */
39 import ch.boye.httpclientandroidlib.HttpClientConnection;
40 import ch.boye.httpclientandroidlib.HttpHost;
41 import ch.boye.httpclientandroidlib.annotation.GuardedBy;
42 import ch.boye.httpclientandroidlib.annotation.ThreadSafe;
43 import ch.boye.httpclientandroidlib.config.ConnectionConfig;
44 import ch.boye.httpclientandroidlib.config.Lookup;
45 import ch.boye.httpclientandroidlib.config.Registry;
46 import ch.boye.httpclientandroidlib.config.RegistryBuilder;
47 import ch.boye.httpclientandroidlib.config.SocketConfig;
48 import ch.boye.httpclientandroidlib.conn.ConnectionRequest;
49 import ch.boye.httpclientandroidlib.conn.DnsResolver;
50 import ch.boye.httpclientandroidlib.conn.HttpClientConnectionManager;
51 import ch.boye.httpclientandroidlib.conn.HttpConnectionFactory;
52 import ch.boye.httpclientandroidlib.conn.SchemePortResolver;
53 import ch.boye.httpclientandroidlib.conn.ManagedHttpClientConnection;
54 import ch.boye.httpclientandroidlib.conn.routing.HttpRoute;
55 import ch.boye.httpclientandroidlib.conn.socket.ConnectionSocketFactory;
56 import ch.boye.httpclientandroidlib.conn.socket.PlainConnectionSocketFactory;
57 import ch.boye.httpclientandroidlib.conn.ssl.SSLConnectionSocketFactory;
58 import ch.boye.httpclientandroidlib.protocol.HttpContext;
59 import ch.boye.httpclientandroidlib.util.Args;
60 import ch.boye.httpclientandroidlib.util.Asserts;
61 import ch.boye.httpclientandroidlib.util.LangUtils;
62 
63 /**
64  * A connection manager for a single connection. This connection manager maintains only one active
65  * connection. Even though this class is fully thread-safe it ought to be used by one execution
66  * thread only, as only one thread a time can lease the connection at a time.
67  * <p/>
68  * This connection manager will make an effort to reuse the connection for subsequent requests
69  * with the same {@link HttpRoute route}. It will, however, close the existing connection and
70  * open it for the given route, if the route of the persistent connection does not match that
71  * of the connection request. If the connection has been already been allocated
72  * {@link IllegalStateException} is thrown.
73  * <p/>
74  * This connection manager implementation should be used inside an EJB container instead of
75  * {@link PoolingHttpClientConnectionManager}.
76  *
77  * @since 4.3
78  */
79 @ThreadSafe
80 public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable {
81 
82     public HttpClientAndroidLog log = new HttpClientAndroidLog(getClass());
83 
84     private final HttpClientConnectionOperator connectionOperator;
85     private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory;
86 
87     @GuardedBy("this")
88     private ManagedHttpClientConnection conn;
89 
90     @GuardedBy("this")
91     private HttpRoute route;
92 
93     @GuardedBy("this")
94     private Object state;
95 
96     @GuardedBy("this")
97     private long updated;
98 
99     @GuardedBy("this")
100     private long expiry;
101 
102     @GuardedBy("this")
103     private boolean leased;
104 
105     @GuardedBy("this")
106     private SocketConfig socketConfig;
107 
108     @GuardedBy("this")
109     private ConnectionConfig connConfig;
110 
111     private final AtomicBoolean isShutdown;
112 
getDefaultRegistry()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 
BasicHttpClientConnectionManager( final Lookup<ConnectionSocketFactory> socketFactoryRegistry, final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, final SchemePortResolver schemePortResolver, final DnsResolver dnsResolver)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         this.isShutdown = new AtomicBoolean(false);
133     }
134 
BasicHttpClientConnectionManager( final Lookup<ConnectionSocketFactory> socketFactoryRegistry, final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory)135     public BasicHttpClientConnectionManager(
136             final Lookup<ConnectionSocketFactory> socketFactoryRegistry,
137             final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) {
138         this(socketFactoryRegistry, connFactory, null, null);
139     }
140 
BasicHttpClientConnectionManager( final Lookup<ConnectionSocketFactory> socketFactoryRegistry)141     public BasicHttpClientConnectionManager(
142             final Lookup<ConnectionSocketFactory> socketFactoryRegistry) {
143         this(socketFactoryRegistry, null, null, null);
144     }
145 
BasicHttpClientConnectionManager()146     public BasicHttpClientConnectionManager() {
147         this(getDefaultRegistry(), null, null, null);
148     }
149 
150     @Override
finalize()151     protected void finalize() throws Throwable {
152         try {
153             shutdown();
154         } finally { // Make sure we call overridden method even if shutdown barfs
155             super.finalize();
156         }
157     }
158 
close()159     public void close() {
160         shutdown();
161     }
162 
getRoute()163     HttpRoute getRoute() {
164         return route;
165     }
166 
getState()167     Object getState() {
168         return state;
169     }
170 
getSocketConfig()171     public synchronized SocketConfig getSocketConfig() {
172         return socketConfig;
173     }
174 
setSocketConfig(final SocketConfig socketConfig)175     public synchronized void setSocketConfig(final SocketConfig socketConfig) {
176         this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT;
177     }
178 
getConnectionConfig()179     public synchronized ConnectionConfig getConnectionConfig() {
180         return connConfig;
181     }
182 
setConnectionConfig(final ConnectionConfig connConfig)183     public synchronized void setConnectionConfig(final ConnectionConfig connConfig) {
184         this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT;
185     }
186 
requestConnection( final HttpRoute route, final Object state)187     public final ConnectionRequest requestConnection(
188             final HttpRoute route,
189             final Object state) {
190         Args.notNull(route, "Route");
191         return new ConnectionRequest() {
192 
193             public boolean cancel() {
194                 // Nothing to abort, since requests are immediate.
195                 return false;
196             }
197 
198             public HttpClientConnection get(final long timeout, final TimeUnit tunit) {
199                 return BasicHttpClientConnectionManager.this.getConnection(
200                         route, state);
201             }
202 
203         };
204     }
205 
206     private void closeConnection() {
207         if (this.conn != null) {
208             this.log.debug("Closing connection");
209             try {
210                 this.conn.close();
211             } catch (final IOException iox) {
212                 if (this.log.isDebugEnabled()) {
213                     this.log.debug("I/O exception closing connection", iox);
214                 }
215             }
216             this.conn = null;
217         }
218     }
219 
220     private void shutdownConnection() {
221         if (this.conn != null) {
222             this.log.debug("Shutting down connection");
223             try {
224                 this.conn.shutdown();
225             } catch (final IOException iox) {
226                 if (this.log.isDebugEnabled()) {
227                     this.log.debug("I/O exception shutting down connection", iox);
228                 }
229             }
230             this.conn = null;
231         }
232     }
233 
234     private void checkExpiry() {
235         if (this.conn != null && System.currentTimeMillis() >= this.expiry) {
236             if (this.log.isDebugEnabled()) {
237                 this.log.debug("Connection expired @ " + new Date(this.expiry));
238             }
239             closeConnection();
240         }
241     }
242 
243     synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) {
244         Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down");
245         if (this.log.isDebugEnabled()) {
246             this.log.debug("Get connection for route " + route);
247         }
248         Asserts.check(!this.leased, "Connection is still allocated");
249         if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) {
250             closeConnection();
251         }
252         this.route = route;
253         this.state = state;
254         checkExpiry();
255         if (this.conn == null) {
256             this.conn = this.connFactory.create(route, this.connConfig);
257         }
258         this.leased = true;
259         return this.conn;
260     }
261 
262     public synchronized void releaseConnection(
263             final HttpClientConnection conn,
264             final Object state,
265             final long keepalive, final TimeUnit tunit) {
266         Args.notNull(conn, "Connection");
267         Asserts.check(conn == this.conn, "Connection not obtained from this manager");
268         if (this.log.isDebugEnabled()) {
269             this.log.debug("Releasing connection " + conn);
270         }
271         if (this.isShutdown.get()) {
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.isShutdown.get()) {
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.isShutdown.get()) {
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.isShutdown.compareAndSet(false, true)) {
366             shutdownConnection();
367         }
368     }
369 
370 }
371