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