001/* 002 * ==================================================================== 003 * Licensed to the Apache Software Foundation (ASF) under one 004 * or more contributor license agreements. See the NOTICE file 005 * distributed with this work for additional information 006 * regarding copyright ownership. The ASF licenses this file 007 * to you under the Apache License, Version 2.0 (the 008 * "License"); you may not use this file except in compliance 009 * with the License. You may obtain a copy of the License at 010 * 011 * http://www.apache.org/licenses/LICENSE-2.0 012 * 013 * Unless required by applicable law or agreed to in writing, 014 * software distributed under the License is distributed on an 015 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 016 * KIND, either express or implied. See the License for the 017 * specific language governing permissions and limitations 018 * under the License. 019 * ==================================================================== 020 * 021 * This software consists of voluntary contributions made by many 022 * individuals on behalf of the Apache Software Foundation. For more 023 * information on the Apache Software Foundation, please see 024 * <http://www.apache.org/>. 025 * 026 */ 027 028package org.apache.http.impl.conn; 029 030import java.io.Closeable; 031import java.io.IOException; 032import java.net.InetSocketAddress; 033import java.util.Date; 034import java.util.concurrent.TimeUnit; 035import java.util.concurrent.atomic.AtomicBoolean; 036 037import org.apache.commons.logging.Log; 038import org.apache.commons.logging.LogFactory; 039import org.apache.http.HttpClientConnection; 040import org.apache.http.HttpHost; 041import org.apache.http.annotation.Contract; 042import org.apache.http.annotation.ThreadingBehavior; 043import org.apache.http.config.ConnectionConfig; 044import org.apache.http.config.Lookup; 045import org.apache.http.config.Registry; 046import org.apache.http.config.RegistryBuilder; 047import org.apache.http.config.SocketConfig; 048import org.apache.http.conn.ConnectionRequest; 049import org.apache.http.conn.DnsResolver; 050import org.apache.http.conn.HttpClientConnectionManager; 051import org.apache.http.conn.HttpClientConnectionOperator; 052import org.apache.http.conn.HttpConnectionFactory; 053import org.apache.http.conn.ManagedHttpClientConnection; 054import org.apache.http.conn.SchemePortResolver; 055import org.apache.http.conn.routing.HttpRoute; 056import org.apache.http.conn.socket.ConnectionSocketFactory; 057import org.apache.http.conn.socket.PlainConnectionSocketFactory; 058import org.apache.http.conn.ssl.SSLConnectionSocketFactory; 059import org.apache.http.protocol.HttpContext; 060import org.apache.http.util.Args; 061import org.apache.http.util.Asserts; 062import org.apache.http.util.LangUtils; 063 064/** 065 * A connection manager for a single connection. This connection manager maintains only one active 066 * connection. Even though this class is fully thread-safe it ought to be used by one execution 067 * thread only, as only one thread a time can lease the connection at a time. 068 * <p> 069 * This connection manager will make an effort to reuse the connection for subsequent requests 070 * with the same {@link HttpRoute route}. It will, however, close the existing connection and 071 * open it for the given route, if the route of the persistent connection does not match that 072 * of the connection request. If the connection has been already been allocated 073 * {@link IllegalStateException} is thrown. 074 * </p> 075 * <p> 076 * This connection manager implementation should be used inside an EJB container instead of 077 * {@link PoolingHttpClientConnectionManager}. 078 * </p> 079 * 080 * @since 4.3 081 */ 082@Contract(threading = ThreadingBehavior.SAFE_CONDITIONAL) 083public class BasicHttpClientConnectionManager implements HttpClientConnectionManager, Closeable { 084 085 private final Log log = LogFactory.getLog(getClass()); 086 087 private final HttpClientConnectionOperator connectionOperator; 088 private final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory; 089 090 private ManagedHttpClientConnection conn; 091 private HttpRoute route; 092 private Object state; 093 private long updated; 094 private long expiry; 095 private boolean leased; 096 private SocketConfig socketConfig; 097 private ConnectionConfig connConfig; 098 099 private final AtomicBoolean isShutdown; 100 101 private static Registry<ConnectionSocketFactory> getDefaultRegistry() { 102 return RegistryBuilder.<ConnectionSocketFactory>create() 103 .register("http", PlainConnectionSocketFactory.getSocketFactory()) 104 .register("https", SSLConnectionSocketFactory.getSocketFactory()) 105 .build(); 106 } 107 108 public BasicHttpClientConnectionManager( 109 final Lookup<ConnectionSocketFactory> socketFactoryRegistry, 110 final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory, 111 final SchemePortResolver schemePortResolver, 112 final DnsResolver dnsResolver) { 113 this( 114 new DefaultHttpClientConnectionOperator(socketFactoryRegistry, schemePortResolver, dnsResolver), 115 connFactory 116 ); 117 } 118 119 /** 120 * @since 4.4 121 */ 122 public BasicHttpClientConnectionManager( 123 final HttpClientConnectionOperator httpClientConnectionOperator, 124 final HttpConnectionFactory<HttpRoute, ManagedHttpClientConnection> connFactory) { 125 super(); 126 this.connectionOperator = Args.notNull(httpClientConnectionOperator, "Connection operator"); 127 this.connFactory = connFactory != null ? connFactory : ManagedHttpClientConnectionFactory.INSTANCE; 128 this.expiry = Long.MAX_VALUE; 129 this.socketConfig = SocketConfig.DEFAULT; 130 this.connConfig = ConnectionConfig.DEFAULT; 131 this.isShutdown = new AtomicBoolean(false); 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 @Override 159 public void close() { 160 shutdown(); 161 } 162 163 HttpRoute getRoute() { 164 return route; 165 } 166 167 Object getState() { 168 return state; 169 } 170 171 public synchronized SocketConfig getSocketConfig() { 172 return socketConfig; 173 } 174 175 public synchronized void setSocketConfig(final SocketConfig socketConfig) { 176 this.socketConfig = socketConfig != null ? socketConfig : SocketConfig.DEFAULT; 177 } 178 179 public synchronized ConnectionConfig getConnectionConfig() { 180 return connConfig; 181 } 182 183 public synchronized void setConnectionConfig(final ConnectionConfig connConfig) { 184 this.connConfig = connConfig != null ? connConfig : ConnectionConfig.DEFAULT; 185 } 186 187 @Override 188 public final ConnectionRequest requestConnection( 189 final HttpRoute route, 190 final Object state) { 191 Args.notNull(route, "Route"); 192 return new ConnectionRequest() { 193 194 @Override 195 public boolean cancel() { 196 // Nothing to abort, since requests are immediate. 197 return false; 198 } 199 200 @Override 201 public HttpClientConnection get(final long timeout, final TimeUnit tunit) { 202 return BasicHttpClientConnectionManager.this.getConnection( 203 route, state); 204 } 205 206 }; 207 } 208 209 private void closeConnection() { 210 if (this.conn != null) { 211 this.log.debug("Closing connection"); 212 try { 213 this.conn.close(); 214 } catch (final IOException iox) { 215 if (this.log.isDebugEnabled()) { 216 this.log.debug("I/O exception closing connection", iox); 217 } 218 } 219 this.conn = null; 220 } 221 } 222 223 private void shutdownConnection() { 224 if (this.conn != null) { 225 this.log.debug("Shutting down connection"); 226 try { 227 this.conn.shutdown(); 228 } catch (final IOException iox) { 229 if (this.log.isDebugEnabled()) { 230 this.log.debug("I/O exception shutting down connection", iox); 231 } 232 } 233 this.conn = null; 234 } 235 } 236 237 private void checkExpiry() { 238 if (this.conn != null && System.currentTimeMillis() >= this.expiry) { 239 if (this.log.isDebugEnabled()) { 240 this.log.debug("Connection expired @ " + new Date(this.expiry)); 241 } 242 closeConnection(); 243 } 244 } 245 246 synchronized HttpClientConnection getConnection(final HttpRoute route, final Object state) { 247 Asserts.check(!this.isShutdown.get(), "Connection manager has been shut down"); 248 if (this.log.isDebugEnabled()) { 249 this.log.debug("Get connection for route " + route); 250 } 251 Asserts.check(!this.leased, "Connection is still allocated"); 252 if (!LangUtils.equals(this.route, route) || !LangUtils.equals(this.state, state)) { 253 closeConnection(); 254 } 255 this.route = route; 256 this.state = state; 257 checkExpiry(); 258 if (this.conn == null) { 259 this.conn = this.connFactory.create(route, this.connConfig); 260 } 261 this.leased = true; 262 return this.conn; 263 } 264 265 @Override 266 public synchronized void releaseConnection( 267 final HttpClientConnection conn, 268 final Object state, 269 final long keepalive, final TimeUnit tunit) { 270 Args.notNull(conn, "Connection"); 271 Asserts.check(conn == this.conn, "Connection not obtained from this manager"); 272 if (this.log.isDebugEnabled()) { 273 this.log.debug("Releasing connection " + conn); 274 } 275 if (this.isShutdown.get()) { 276 return; 277 } 278 try { 279 this.updated = System.currentTimeMillis(); 280 if (!this.conn.isOpen()) { 281 this.conn = null; 282 this.route = null; 283 this.conn = null; 284 this.expiry = Long.MAX_VALUE; 285 } else { 286 this.state = state; 287 if (this.log.isDebugEnabled()) { 288 final String s; 289 if (keepalive > 0) { 290 s = "for " + keepalive + " " + tunit; 291 } else { 292 s = "indefinitely"; 293 } 294 this.log.debug("Connection can be kept alive " + s); 295 } 296 if (keepalive > 0) { 297 this.expiry = this.updated + tunit.toMillis(keepalive); 298 } else { 299 this.expiry = Long.MAX_VALUE; 300 } 301 } 302 } finally { 303 this.leased = false; 304 } 305 } 306 307 @Override 308 public void connect( 309 final HttpClientConnection conn, 310 final HttpRoute route, 311 final int connectTimeout, 312 final HttpContext context) throws IOException { 313 Args.notNull(conn, "Connection"); 314 Args.notNull(route, "HTTP route"); 315 Asserts.check(conn == this.conn, "Connection not obtained from this manager"); 316 final HttpHost host; 317 if (route.getProxyHost() != null) { 318 host = route.getProxyHost(); 319 } else { 320 host = route.getTargetHost(); 321 } 322 final InetSocketAddress localAddress = route.getLocalSocketAddress(); 323 this.connectionOperator.connect(this.conn, host, localAddress, 324 connectTimeout, this.socketConfig, context); 325 } 326 327 @Override 328 public void upgrade( 329 final HttpClientConnection conn, 330 final HttpRoute route, 331 final HttpContext context) throws IOException { 332 Args.notNull(conn, "Connection"); 333 Args.notNull(route, "HTTP route"); 334 Asserts.check(conn == this.conn, "Connection not obtained from this manager"); 335 this.connectionOperator.upgrade(this.conn, route.getTargetHost(), context); 336 } 337 338 @Override 339 public void routeComplete( 340 final HttpClientConnection conn, 341 final HttpRoute route, 342 final HttpContext context) throws IOException { 343 } 344 345 @Override 346 public synchronized void closeExpiredConnections() { 347 if (this.isShutdown.get()) { 348 return; 349 } 350 if (!this.leased) { 351 checkExpiry(); 352 } 353 } 354 355 @Override 356 public synchronized void closeIdleConnections(final long idletime, final TimeUnit tunit) { 357 Args.notNull(tunit, "Time unit"); 358 if (this.isShutdown.get()) { 359 return; 360 } 361 if (!this.leased) { 362 long time = tunit.toMillis(idletime); 363 if (time < 0) { 364 time = 0; 365 } 366 final long deadline = System.currentTimeMillis() - time; 367 if (this.updated <= deadline) { 368 closeConnection(); 369 } 370 } 371 } 372 373 @Override 374 public synchronized void shutdown() { 375 if (this.isShutdown.compareAndSet(false, true)) { 376 shutdownConnection(); 377 } 378 } 379 380}