diff options
Diffstat (limited to 'src/org/apache/http/impl/conn')
28 files changed, 5506 insertions, 0 deletions
diff --git a/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java b/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java new file mode 100644 index 0000000..5cbe010 --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractClientConnAdapter.java @@ -0,0 +1,399 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractClientConnAdapter.java $ + * $Revision: 672969 $ + * $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; +import java.io.InterruptedIOException; +import java.net.InetAddress; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +import javax.net.ssl.SSLSocket; +import javax.net.ssl.SSLSession; + +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpEntityEnclosingRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpConnectionMetrics; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.ClientConnectionManager; + + +/** + * Abstract adapter from {@link OperatedClientConnection operated} to + * {@link ManagedClientConnection managed} client connections. + * Read and write methods are delegated to the wrapped connection. + * Operations affecting the connection state have to be implemented + * by derived classes. Operations for querying the connection state + * are delegated to the wrapped connection if there is one, or + * return a default value if there is none. + * <br/> + * This adapter tracks the checkpoints for reusable communication states, + * as indicated by {@link #markReusable markReusable} and queried by + * {@link #isMarkedReusable isMarkedReusable}. + * All send and receive operations will automatically clear the mark. + * <br/> + * Connection release calls are delegated to the connection manager, + * if there is one. {@link #abortConnection abortConnection} will + * clear the reusability mark first. The connection manager is + * expected to tolerate multiple calls to the release method. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 672969 $ $Date: 2008-06-30 18:09:50 -0700 (Mon, 30 Jun 2008) $ + * + * @since 4.0 + */ +public abstract class AbstractClientConnAdapter + implements ManagedClientConnection { + + /** Thread that requested this connection. */ + private final Thread executionThread; + + /** + * The connection manager, if any. + * This attribute MUST NOT be final, so the adapter can be detached + * from the connection manager without keeping a hard reference there. + */ + private volatile ClientConnectionManager connManager; + + /** The wrapped connection. */ + private volatile OperatedClientConnection wrappedConnection; + + /** The reusability marker. */ + private volatile boolean markedReusable; + + /** True if the connection has been aborted. */ + private volatile boolean aborted; + + /** The duration this is valid for while idle (in ms). */ + private volatile long duration; + + /** + * Creates a new connection adapter. + * The adapter is initially <i>not</i> + * {@link #isMarkedReusable marked} as reusable. + * + * @param mgr the connection manager, or <code>null</code> + * @param conn the connection to wrap, or <code>null</code> + */ + protected AbstractClientConnAdapter(ClientConnectionManager mgr, + OperatedClientConnection conn) { + super(); + executionThread = Thread.currentThread(); + connManager = mgr; + wrappedConnection = conn; + markedReusable = false; + aborted = false; + duration = Long.MAX_VALUE; + } // <constructor> + + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + protected void detach() { + wrappedConnection = null; + connManager = null; // base class attribute + duration = Long.MAX_VALUE; + } + + protected OperatedClientConnection getWrappedConnection() { + return wrappedConnection; + } + + protected ClientConnectionManager getManager() { + return connManager; + } + + /** + * Asserts that the connection has not been aborted. + * + * @throws InterruptedIOException if the connection has been aborted + */ + protected final void assertNotAborted() throws InterruptedIOException { + if (aborted) { + throw new InterruptedIOException("Connection has been shut down."); + } + } + + /** + * Asserts that there is a wrapped connection to delegate to. + * + * @throws IllegalStateException if there is no wrapped connection + * or connection has been aborted + */ + protected final void assertValid( + final OperatedClientConnection wrappedConn) { + if (wrappedConn == null) { + throw new IllegalStateException("No wrapped connection."); + } + } + + // non-javadoc, see interface HttpConnection + public boolean isOpen() { + OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) + return false; + + return conn.isOpen(); + } + + + // non-javadoc, see interface HttpConnection + public boolean isStale() { + if (aborted) + return true; + OperatedClientConnection conn = getWrappedConnection(); + if (conn == null) + return true; + + return conn.isStale(); + } + + + // non-javadoc, see interface HttpConnection + public void setSocketTimeout(int timeout) { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + conn.setSocketTimeout(timeout); + } + + + // non-javadoc, see interface HttpConnection + public int getSocketTimeout() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getSocketTimeout(); + } + + + // non-javadoc, see interface HttpConnection + public HttpConnectionMetrics getMetrics() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getMetrics(); + } + + + // non-javadoc, see interface HttpClientConnection + public void flush() + throws IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + conn.flush(); + } + + + // non-javadoc, see interface HttpClientConnection + public boolean isResponseAvailable(int timeout) + throws IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + return conn.isResponseAvailable(timeout); + } + + + // non-javadoc, see interface HttpClientConnection + public void receiveResponseEntity(HttpResponse response) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.receiveResponseEntity(response); + } + + + // non-javadoc, see interface HttpClientConnection + public HttpResponse receiveResponseHeader() + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + return conn.receiveResponseHeader(); + } + + + // non-javadoc, see interface HttpClientConnection + public void sendRequestEntity(HttpEntityEnclosingRequest request) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.sendRequestEntity(request); + } + + + // non-javadoc, see interface HttpClientConnection + public void sendRequestHeader(HttpRequest request) + throws HttpException, IOException { + + assertNotAborted(); + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + + unmarkReusable(); + conn.sendRequestHeader(request); + } + + + // non-javadoc, see interface HttpInetConnection + public InetAddress getLocalAddress() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalAddress(); + } + + // non-javadoc, see interface HttpInetConnection + public int getLocalPort() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getLocalPort(); + } + + + // non-javadoc, see interface HttpInetConnection + public InetAddress getRemoteAddress() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemoteAddress(); + } + + // non-javadoc, see interface HttpInetConnection + public int getRemotePort() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.getRemotePort(); + } + + // non-javadoc, see interface ManagedClientConnection + public boolean isSecure() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + return conn.isSecure(); + } + + // non-javadoc, see interface ManagedClientConnection + public SSLSession getSSLSession() { + OperatedClientConnection conn = getWrappedConnection(); + assertValid(conn); + if (!isOpen()) + return null; + + SSLSession result = null; + Socket sock = conn.getSocket(); + if (sock instanceof SSLSocket) { + result = ((SSLSocket)sock).getSession(); + } + return result; + } + + // non-javadoc, see interface ManagedClientConnection + public void markReusable() { + markedReusable = true; + } + + // non-javadoc, see interface ManagedClientConnection + public void unmarkReusable() { + markedReusable = false; + } + + // non-javadoc, see interface ManagedClientConnection + public boolean isMarkedReusable() { + return markedReusable; + } + + public void setIdleDuration(long duration, TimeUnit unit) { + if(duration > 0) { + this.duration = unit.toMillis(duration); + } else { + this.duration = -1; + } + } + + // non-javadoc, see interface ConnectionReleaseTrigger + public void releaseConnection() { + if (connManager != null) { + connManager.releaseConnection(this, duration, TimeUnit.MILLISECONDS); + } + } + + // non-javadoc, see interface ConnectionReleaseTrigger + public void abortConnection() { + if (aborted) { + return; + } + aborted = true; + unmarkReusable(); + try { + shutdown(); + } catch (IOException ignore) { + } + // Usually #abortConnection() is expected to be called from + // a helper thread in order to unblock the main execution thread + // blocked in an I/O operation. It may be unsafe to call + // #releaseConnection() from the helper thread, so we have to rely + // on an IOException thrown by the closed socket on the main thread + // to trigger the release of the connection back to the + // connection manager. + // + // However, if this method is called from the main execution thread + // it should be safe to release the connection immediately. Besides, + // this also helps ensure the connection gets released back to the + // manager if #abortConnection() is called from the main execution + // thread while there is no blocking I/O operation. + if (executionThread.equals(Thread.currentThread())) { + releaseConnection(); + } + } + +} // class AbstractClientConnAdapter diff --git a/src/org/apache/http/impl/conn/AbstractPoolEntry.java b/src/org/apache/http/impl/conn/AbstractPoolEntry.java new file mode 100644 index 0000000..0e7d95f --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractPoolEntry.java @@ -0,0 +1,322 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractPoolEntry.java $ + * $Revision: 658775 $ + * $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.RouteTracker; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.OperatedClientConnection; + + + +/** + * A pool entry for use by connection manager implementations. + * Pool entries work in conjunction with an + * {@link AbstractClientConnAdapter adapter}. + * The adapter is handed out to applications that obtain a connection. + * The pool entry stores the underlying connection and tracks the + * {@link HttpRoute route} established. + * The adapter delegates methods for establishing the route to + * it's pool entry. + * <br/> + * If the managed connections is released or revoked, the adapter + * gets disconnected, but the pool entry still contains the + * underlying connection and the established route. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 658775 $ + * + * @since 4.0 + */ +public abstract class AbstractPoolEntry { + + /** The connection operator. */ + protected final ClientConnectionOperator connOperator; + + /** The underlying connection being pooled or used. */ + protected final OperatedClientConnection connection; + + /** The route for which this entry gets allocated. */ + //@@@ currently accessed from connection manager(s) as attribute + //@@@ avoid that, derived classes should decide whether update is allowed + //@@@ SCCM: yes, TSCCM: no + protected volatile HttpRoute route; + + /** Connection state object */ + protected volatile Object state; + + /** The tracked route, or <code>null</code> before tracking starts. */ + protected volatile RouteTracker tracker; + + + /** + * Creates a new pool entry. + * + * @param connOperator the Connection Operator for this entry + * @param route the planned route for the connection, + * or <code>null</code> + */ + protected AbstractPoolEntry(ClientConnectionOperator connOperator, + HttpRoute route) { + super(); + if (connOperator == null) { + throw new IllegalArgumentException("Connection operator may not be null"); + } + this.connOperator = connOperator; + this.connection = connOperator.createConnection(); + this.route = route; + this.tracker = null; + } + + /** + * Returns the state object associated with this pool entry. + * + * @return The state object + */ + public Object getState() { + return state; + } + + /** + * Assigns a state object to this pool entry. + * + * @param state The state object + */ + public void setState(final Object state) { + this.state = state; + } + + /** + * Opens the underlying connection. + * + * @param route the route along which to open the connection + * @param context the context for opening the connection + * @param params the parameters for opening the connection + * + * @throws IOException in case of a problem + */ + public void open(HttpRoute route, + HttpContext context, HttpParams params) + throws IOException { + + if (route == null) { + throw new IllegalArgumentException + ("Route must not be null."); + } + //@@@ is context allowed to be null? depends on operator? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if ((this.tracker != null) && this.tracker.isConnected()) { + throw new IllegalStateException("Connection already open."); + } + + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // opening of the connection will be tracked. + + //@@@ verify route against planned route? + + this.tracker = new RouteTracker(route); + final HttpHost proxy = route.getProxyHost(); + + connOperator.openConnection + (this.connection, + (proxy != null) ? proxy : route.getTargetHost(), + route.getLocalAddress(), + context, params); + + RouteTracker localTracker = tracker; // capture volatile + + // If this tracker was reset while connecting, + // fail early. + if (localTracker == null) { + throw new IOException("Request aborted"); + } + + if (proxy == null) { + localTracker.connectTarget(this.connection.isSecure()); + } else { + localTracker.connectProxy(proxy, this.connection.isSecure()); + } + + } // open + + + /** + * Tracks tunnelling of the connection to the target. + * The tunnel has to be established outside by sending a CONNECT + * request to the (last) proxy. + * + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelTarget(boolean secure, HttpParams params) + throws IOException { + + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + //@@@ check for proxy in planned route? + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + if (this.tracker.isTunnelled()) { + throw new IllegalStateException + ("Connection is already tunnelled."); + } + + // LOG.debug? + + this.connection.update(null, tracker.getTargetHost(), + secure, params); + this.tracker.tunnelTarget(secure); + + } // tunnelTarget + + + /** + * Tracks tunnelling of the connection to a chained proxy. + * The tunnel has to be established outside by sending a CONNECT + * request to the previous proxy. + * + * @param next the proxy to which the tunnel was established. + * See {@link org.apache.http.conn.ManagedClientConnection#tunnelProxy + * ManagedClientConnection.tunnelProxy} + * for details. + * @param secure <code>true</code> if the tunnel should be + * considered secure, <code>false</code> otherwise + * @param params the parameters for tunnelling the connection + * + * @throws IOException in case of a problem + */ + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException { + + if (next == null) { + throw new IllegalArgumentException + ("Next proxy must not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + //@@@ check for proxy in planned route? + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + + // LOG.debug? + + this.connection.update(null, next, secure, params); + this.tracker.tunnelProxy(next, secure); + + } // tunnelProxy + + + /** + * Layers a protocol on top of an established tunnel. + * + * @param context the context for layering + * @param params the parameters for layering + * + * @throws IOException in case of a problem + */ + public void layerProtocol(HttpContext context, HttpParams params) + throws IOException { + + //@@@ is context allowed to be null? depends on operator? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + if ((this.tracker == null) || !this.tracker.isConnected()) { + throw new IllegalStateException("Connection not open."); + } + if (!this.tracker.isTunnelled()) { + //@@@ allow this? + throw new IllegalStateException + ("Protocol layering without a tunnel not supported."); + } + if (this.tracker.isLayered()) { + throw new IllegalStateException + ("Multiple protocol layering not supported."); + } + + // - collect the arguments + // - call the operator + // - update the tracking data + // In this order, we can be sure that only a successful + // layering on top of the connection will be tracked. + + final HttpHost target = tracker.getTargetHost(); + + connOperator.updateSecureConnection(this.connection, target, + context, params); + + this.tracker.layerProtocol(this.connection.isSecure()); + + } // layerProtocol + + + /** + * Shuts down the entry. + * + * If {@link #open(HttpRoute, HttpContext, HttpParams)} is in progress, + * this will cause that open to possibly throw an {@link IOException}. + */ + protected void shutdownEntry() { + tracker = null; + } + + +} // class AbstractPoolEntry + diff --git a/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java b/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java new file mode 100644 index 0000000..2c5fd30 --- /dev/null +++ b/src/org/apache/http/impl/conn/AbstractPooledConnAdapter.java @@ -0,0 +1,188 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/AbstractPooledConnAdapter.java $ + * $Revision: 658775 $ + * $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HttpContext; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.OperatedClientConnection; + + + +/** + * Abstract adapter from pool {@link AbstractPoolEntry entries} to + * {@link org.apache.http.conn.ManagedClientConnection managed} + * client connections. + * The connection in the pool entry is used to initialize the base class. + * In addition, methods to establish a route are delegated to the + * pool entry. {@link #shutdown shutdown} and {@link #close close} + * will clear the tracked route in the pool entry and call the + * respective method of the wrapped connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 658775 $ $Date: 2008-05-21 10:30:45 -0700 (Wed, 21 May 2008) $ + * + * @since 4.0 + */ +public abstract class AbstractPooledConnAdapter extends AbstractClientConnAdapter { + + /** The wrapped pool entry. */ + protected volatile AbstractPoolEntry poolEntry; + + + /** + * Creates a new connection adapter. + * + * @param manager the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected AbstractPooledConnAdapter(ClientConnectionManager manager, + AbstractPoolEntry entry) { + super(manager, entry.connection); + this.poolEntry = entry; + } + + + /** + * Asserts that this adapter is still attached. + * + * @throws IllegalStateException + * if it is {@link #detach detach}ed + */ + protected final void assertAttached() { + if (poolEntry == null) { + throw new IllegalStateException("Adapter is detached."); + } + } + + /** + * Detaches this adapter from the wrapped connection. + * This adapter becomes useless. + */ + @Override + protected void detach() { + super.detach(); + poolEntry = null; + } + + + // non-javadoc, see interface ManagedHttpConnection + public HttpRoute getRoute() { + + assertAttached(); + return (poolEntry.tracker == null) ? + null : poolEntry.tracker.toRoute(); + } + + // non-javadoc, see interface ManagedHttpConnection + public void open(HttpRoute route, + HttpContext context, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.open(route, context, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void tunnelTarget(boolean secure, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.tunnelTarget(secure, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void tunnelProxy(HttpHost next, boolean secure, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.tunnelProxy(next, secure, params); + } + + + // non-javadoc, see interface ManagedHttpConnection + public void layerProtocol(HttpContext context, HttpParams params) + throws IOException { + + assertAttached(); + poolEntry.layerProtocol(context, params); + } + + + + // non-javadoc, see interface HttpConnection + public void close() throws IOException { + if (poolEntry != null) + poolEntry.shutdownEntry(); + + OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.close(); + } + } + + // non-javadoc, see interface HttpConnection + public void shutdown() throws IOException { + if (poolEntry != null) + poolEntry.shutdownEntry(); + + OperatedClientConnection conn = getWrappedConnection(); + if (conn != null) { + conn.shutdown(); + } + } + + + // non-javadoc, see interface ManagedClientConnection + public Object getState() { + assertAttached(); + return poolEntry.getState(); + } + + + // non-javadoc, see interface ManagedClientConnection + public void setState(final Object state) { + assertAttached(); + poolEntry.setState(state); + } + + +} // class AbstractPooledConnAdapter diff --git a/src/org/apache/http/impl/conn/DefaultClientConnection.java b/src/org/apache/http/impl/conn/DefaultClientConnection.java new file mode 100644 index 0000000..a41f57a --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultClientConnection.java @@ -0,0 +1,259 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnection.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.io.IOException; +import java.net.Socket; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseFactory; +import org.apache.http.params.HttpParams; +import org.apache.http.impl.SocketHttpClientConnection; +import org.apache.http.io.HttpMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.io.SessionOutputBuffer; + +import org.apache.http.conn.OperatedClientConnection; + + +/** + * Default implementation of an operated client connection. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * @since 4.0 + */ +public class DefaultClientConnection extends SocketHttpClientConnection + implements OperatedClientConnection { + + private final Log log = LogFactory.getLog(getClass()); + private final Log headerLog = LogFactory.getLog("org.apache.http.headers"); + private final Log wireLog = LogFactory.getLog("org.apache.http.wire"); + + /** The unconnected socket */ + private volatile Socket socket; + + /** The target host of this connection. */ + private HttpHost targetHost; + + /** Whether this connection is secure. */ + private boolean connSecure; + + /** True if this connection was shutdown. */ + private volatile boolean shutdown; + + public DefaultClientConnection() { + super(); + } + + + // non-javadoc, see interface OperatedClientConnection + public final HttpHost getTargetHost() { + return this.targetHost; + } + + + // non-javadoc, see interface OperatedClientConnection + public final boolean isSecure() { + return this.connSecure; + } + + + @Override + public final Socket getSocket() { + return this.socket; + } + + + public void opening(Socket sock, HttpHost target) throws IOException { + assertNotOpen(); + this.socket = sock; + this.targetHost = target; + + // Check for shutdown after assigning socket, so that + if (this.shutdown) { + sock.close(); // allow this to throw... + // ...but if it doesn't, explicitly throw one ourselves. + throw new IOException("Connection already shutdown"); + } + } + + + public void openCompleted(boolean secure, HttpParams params) throws IOException { + assertNotOpen(); + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + this.connSecure = secure; + bind(this.socket, params); + } + + /** + * Force-closes this connection. + * If the connection is still in the process of being open (the method + * {@link #opening opening} was already called but + * {@link #openCompleted openCompleted} was not), the associated + * socket that is being connected to a remote address will be closed. + * That will interrupt a thread that is blocked on connecting + * the socket. + * If the connection is not yet open, this will prevent the connection + * from being opened. + * + * @throws IOException in case of a problem + */ + @Override + public void shutdown() throws IOException { + log.debug("Connection shut down"); + shutdown = true; + + super.shutdown(); + Socket sock = this.socket; // copy volatile attribute + if (sock != null) + sock.close(); + + } // shutdown + + + @Override + public void close() throws IOException { + log.debug("Connection closed"); + super.close(); + } + + + @Override + protected SessionInputBuffer createSessionInputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + SessionInputBuffer inbuffer = super.createSessionInputBuffer( + socket, + buffersize, + params); + if (wireLog.isDebugEnabled()) { + inbuffer = new LoggingSessionInputBuffer(inbuffer, new Wire(wireLog)); + } + return inbuffer; + } + + + @Override + protected SessionOutputBuffer createSessionOutputBuffer( + final Socket socket, + int buffersize, + final HttpParams params) throws IOException { + SessionOutputBuffer outbuffer = super.createSessionOutputBuffer( + socket, + buffersize, + params); + if (wireLog.isDebugEnabled()) { + outbuffer = new LoggingSessionOutputBuffer(outbuffer, new Wire(wireLog)); + } + return outbuffer; + } + + + @Override + protected HttpMessageParser createResponseParser( + final SessionInputBuffer buffer, + final HttpResponseFactory responseFactory, + final HttpParams params) { + // override in derived class to specify a line parser + return new DefaultResponseParser + (buffer, null, responseFactory, params); + } + + + // non-javadoc, see interface OperatedClientConnection + public void update(Socket sock, HttpHost target, + boolean secure, HttpParams params) + throws IOException { + + assertOpen(); + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + + if (sock != null) { + this.socket = sock; + bind(sock, params); + } + targetHost = target; + connSecure = secure; + + } // update + + + @Override + public HttpResponse receiveResponseHeader() throws HttpException, IOException { + HttpResponse response = super.receiveResponseHeader(); + if (headerLog.isDebugEnabled()) { + headerLog.debug("<< " + response.getStatusLine().toString()); + Header[] headers = response.getAllHeaders(); + for (Header header : headers) { + headerLog.debug("<< " + header.toString()); + } + } + return response; + } + + + @Override + public void sendRequestHeader(HttpRequest request) throws HttpException, IOException { + super.sendRequestHeader(request); + if (headerLog.isDebugEnabled()) { + headerLog.debug(">> " + request.getRequestLine().toString()); + Header[] headers = request.getAllHeaders(); + for (Header header : headers) { + headerLog.debug(">> " + header.toString()); + } + } + } + +} // class DefaultClientConnection diff --git a/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java b/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java new file mode 100644 index 0000000..41488e1 --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultClientConnectionOperator.java @@ -0,0 +1,216 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultClientConnectionOperator.java $ + * $Revision: 652193 $ + * $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.net.ConnectException; +import java.net.Socket; +import java.net.InetAddress; + +import org.apache.http.HttpHost; +import org.apache.http.params.HttpParams; +import org.apache.http.params.HttpConnectionParams; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.HttpHostConnectException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.scheme.LayeredSocketFactory; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.scheme.SocketFactory; + + +/** + * Default implementation of a + * {@link ClientConnectionOperator ClientConnectionOperator}. + * It uses a {@link SchemeRegistry SchemeRegistry} to look up + * {@link SocketFactory SocketFactory} objects. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 652193 $ $Date: 2008-04-29 17:10:36 -0700 (Tue, 29 Apr 2008) $ + * + * @since 4.0 + */ +public class DefaultClientConnectionOperator + implements ClientConnectionOperator { + + + /** The scheme registry for looking up socket factories. */ + protected SchemeRegistry schemeRegistry; + + + /** + * Creates a new client connection operator for the given scheme registry. + * + * @param schemes the scheme registry + */ + public DefaultClientConnectionOperator(SchemeRegistry schemes) { + if (schemes == null) { + throw new IllegalArgumentException + ("Scheme registry must not be null."); + } + schemeRegistry = schemes; + } + + + // non-javadoc, see interface ClientConnectionOperator + public OperatedClientConnection createConnection() { + return new DefaultClientConnection(); + } + + + // non-javadoc, see interface ClientConnectionOperator + public void openConnection(OperatedClientConnection conn, + HttpHost target, + InetAddress local, + HttpContext context, + HttpParams params) + throws IOException { + + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + // local address may be null + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must not be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + final SocketFactory sf = schm.getSocketFactory(); + + Socket sock = sf.createSocket(); + conn.opening(sock, target); + + try { + sock = sf.connectSocket(sock, target.getHostName(), + schm.resolvePort(target.getPort()), + local, 0, params); + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.openCompleted(sf.isSecure(sock), params); + } // openConnection + + + // non-javadoc, see interface ClientConnectionOperator + public void updateSecureConnection(OperatedClientConnection conn, + HttpHost target, + HttpContext context, + HttpParams params) + throws IOException { + + + if (conn == null) { + throw new IllegalArgumentException + ("Connection must not be null."); + } + if (target == null) { + throw new IllegalArgumentException + ("Target host must not be null."); + } + //@@@ is context allowed to be null? + if (params == null) { + throw new IllegalArgumentException + ("Parameters must not be null."); + } + if (!conn.isOpen()) { + throw new IllegalArgumentException + ("Connection must be open."); + } + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + if (!(schm.getSocketFactory() instanceof LayeredSocketFactory)) { + throw new IllegalArgumentException + ("Target scheme (" + schm.getName() + + ") must have layered socket factory."); + } + + final LayeredSocketFactory lsf = (LayeredSocketFactory) schm.getSocketFactory(); + final Socket sock; + try { + sock = lsf.createSocket + (conn.getSocket(), target.getHostName(), target.getPort(), true); + } catch (ConnectException ex) { + throw new HttpHostConnectException(target, ex); + } + prepareSocket(sock, context, params); + conn.update(sock, target, lsf.isSecure(sock), params); + //@@@ error handling: close the layered socket in case of exception? + + } // updateSecureConnection + + + /** + * Performs standard initializations on a newly created socket. + * + * @param sock the socket to prepare + * @param context the context for the connection + * @param params the parameters from which to prepare the socket + * + * @throws IOException in case of an IO problem + */ + protected void prepareSocket(Socket sock, HttpContext context, + HttpParams params) + throws IOException { + + // context currently not used, but derived classes may need it + //@@@ is context allowed to be null? + + sock.setTcpNoDelay(HttpConnectionParams.getTcpNoDelay(params)); + sock.setSoTimeout(HttpConnectionParams.getSoTimeout(params)); + + int linger = HttpConnectionParams.getLinger(params); + if (linger >= 0) { + sock.setSoLinger(linger > 0, linger); + } + + } // prepareSocket + + +} // class DefaultClientConnectionOperator + diff --git a/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java b/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java new file mode 100644 index 0000000..90fd55f --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java @@ -0,0 +1,121 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultHttpRoutePlanner.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.net.InetAddress; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; + +import org.apache.http.conn.params.ConnRouteParams; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on + * {@link org.apache.http.conn.params.ConnRoutePNames parameters}. + * It will not make use of any Java system properties, + * nor of system or browser proxy settings. + */ +public class DefaultHttpRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected SchemeRegistry schemeRegistry; + + + /** + * Creates a new default route planner. + * + * @param schreg the scheme registry + */ + public DefaultHttpRoutePlanner(SchemeRegistry schreg) { + if (schreg == null) { + throw new IllegalArgumentException + ("SchemeRegistry must not be null."); + } + schemeRegistry = schreg; + } + + + // non-javadoc, see interface HttpRoutePlanner + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (request == null) { + throw new IllegalStateException + ("Request must not be null."); + } + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) + return route; + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + if (target == null) { + throw new IllegalStateException + ("Target host must not be null."); + } + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = + ConnRouteParams.getDefaultProxy(request.getParams()); + + final Scheme schm = schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + +} diff --git a/src/org/apache/http/impl/conn/DefaultResponseParser.java b/src/org/apache/http/impl/conn/DefaultResponseParser.java new file mode 100644 index 0000000..f817a10 --- /dev/null +++ b/src/org/apache/http/impl/conn/DefaultResponseParser.java @@ -0,0 +1,103 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/DefaultResponseParser.java $ + * $Revision: 617638 $ + * $Date: 2008-02-01 12:49:26 -0800 (Fri, 01 Feb 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.HttpException; +import org.apache.http.HttpMessage; +import org.apache.http.HttpResponseFactory; +import org.apache.http.NoHttpResponseException; +import org.apache.http.ProtocolException; +import org.apache.http.StatusLine; +import org.apache.http.conn.params.ConnConnectionPNames; +import org.apache.http.impl.io.AbstractMessageParser; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.message.LineParser; +import org.apache.http.message.ParserCursor; +import org.apache.http.params.HttpParams; +import org.apache.http.util.CharArrayBuffer; + +public class DefaultResponseParser extends AbstractMessageParser { + + private final HttpResponseFactory responseFactory; + private final CharArrayBuffer lineBuf; + private final int maxGarbageLines; + + public DefaultResponseParser( + final SessionInputBuffer buffer, + final LineParser parser, + final HttpResponseFactory responseFactory, + final HttpParams params) { + super(buffer, parser, params); + if (responseFactory == null) { + throw new IllegalArgumentException + ("Response factory may not be null"); + } + this.responseFactory = responseFactory; + this.lineBuf = new CharArrayBuffer(128); + this.maxGarbageLines = params.getIntParameter( + ConnConnectionPNames.MAX_STATUS_LINE_GARBAGE, Integer.MAX_VALUE); + } + + + @Override + protected HttpMessage parseHead( + final SessionInputBuffer sessionBuffer) throws IOException, HttpException { + // clear the buffer + this.lineBuf.clear(); + //read out the HTTP status string + int count = 0; + ParserCursor cursor = null; + do { + int i = sessionBuffer.readLine(this.lineBuf); + if (i == -1 && count == 0) { + // The server just dropped connection on us + throw new NoHttpResponseException("The target server failed to respond"); + } + cursor = new ParserCursor(0, this.lineBuf.length()); + if (lineParser.hasProtocolVersion(this.lineBuf, cursor)) { + // Got one + break; + } else if (i == -1 || count >= this.maxGarbageLines) { + // Giving up + throw new ProtocolException("The server failed to respond with a " + + "valid HTTP response"); + } + count++; + } while(true); + //create the status line from the status string + StatusLine statusline = lineParser.parseStatusLine(this.lineBuf, cursor); + return this.responseFactory.newHttpResponse(statusline, null); + } + +} diff --git a/src/org/apache/http/impl/conn/IdleConnectionHandler.java b/src/org/apache/http/impl/conn/IdleConnectionHandler.java new file mode 100644 index 0000000..2cacda3 --- /dev/null +++ b/src/org/apache/http/impl/conn/IdleConnectionHandler.java @@ -0,0 +1,190 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/IdleConnectionHandler.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.HttpConnection; + + +/** + * A helper class for connection managers to track idle connections. + * + * <p>This class is not synchronized.</p> + * + * @see org.apache.http.conn.ClientConnectionManager#closeIdleConnections + * + * @since 4.0 + */ +public class IdleConnectionHandler { + + private final Log log = LogFactory.getLog(getClass()); + + /** Holds connections and the time they were added. */ + private final Map<HttpConnection,TimeValues> connectionToTimes; + + + public IdleConnectionHandler() { + super(); + connectionToTimes = new HashMap<HttpConnection,TimeValues>(); + } + + /** + * Registers the given connection with this handler. The connection will be held until + * {@link #remove} or {@link #closeIdleConnections} is called. + * + * @param connection the connection to add + * + * @see #remove + */ + public void add(HttpConnection connection, long validDuration, TimeUnit unit) { + + Long timeAdded = Long.valueOf(System.currentTimeMillis()); + + if (log.isDebugEnabled()) { + log.debug("Adding connection at: " + timeAdded); + } + + connectionToTimes.put(connection, new TimeValues(timeAdded, validDuration, unit)); + } + + /** + * Removes the given connection from the list of connections to be closed when idle. + * This will return true if the connection is still valid, and false + * if the connection should be considered expired and not used. + * + * @param connection + * @return True if the connection is still valid. + */ + public boolean remove(HttpConnection connection) { + TimeValues times = connectionToTimes.remove(connection); + if(times == null) { + log.warn("Removing a connection that never existed!"); + return true; + } else { + return System.currentTimeMillis() <= times.timeExpires; + } + } + + /** + * Removes all connections referenced by this handler. + */ + public void removeAll() { + this.connectionToTimes.clear(); + } + + /** + * Closes connections that have been idle for at least the given amount of time. + * + * @param idleTime the minimum idle time, in milliseconds, for connections to be closed + */ + //@@@ add TimeUnit argument here? + public void closeIdleConnections(long idleTime) { + + // the latest time for which connections will be closed + long idleTimeout = System.currentTimeMillis() - idleTime; + + if (log.isDebugEnabled()) { + log.debug("Checking for connections, idleTimeout: " + idleTimeout); + } + + Iterator<HttpConnection> connectionIter = + connectionToTimes.keySet().iterator(); + + while (connectionIter.hasNext()) { + HttpConnection conn = connectionIter.next(); + TimeValues times = connectionToTimes.get(conn); + Long connectionTime = times.timeAdded; + if (connectionTime.longValue() <= idleTimeout) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, connection time: " + connectionTime); + } + connectionIter.remove(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + + public void closeExpiredConnections() { + long now = System.currentTimeMillis(); + if (log.isDebugEnabled()) { + log.debug("Checking for expired connections, now: " + now); + } + + Iterator<HttpConnection> connectionIter = + connectionToTimes.keySet().iterator(); + + while (connectionIter.hasNext()) { + HttpConnection conn = connectionIter.next(); + TimeValues times = connectionToTimes.get(conn); + if(times.timeExpires <= now) { + if (log.isDebugEnabled()) { + log.debug("Closing connection, expired @: " + times.timeExpires); + } + connectionIter.remove(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + } + + private static class TimeValues { + private final long timeAdded; + private final long timeExpires; + + /** + * @param now The current time in milliseconds + * @param validDuration The duration this connection is valid for + * @param validUnit The unit of time the duration is specified in. + */ + TimeValues(long now, long validDuration, TimeUnit validUnit) { + this.timeAdded = now; + if(validDuration > 0) { + this.timeExpires = now + validUnit.toMillis(validDuration); + } else { + this.timeExpires = Long.MAX_VALUE; + } + } + } +} diff --git a/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java b/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java new file mode 100644 index 0000000..4f6477e --- /dev/null +++ b/src/org/apache/http/impl/conn/LoggingSessionInputBuffer.java @@ -0,0 +1,117 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/LoggingSessionInputBuffer.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Logs all data read to the wire LOG. + * + * @author Ortwin Glueck + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class LoggingSessionInputBuffer implements SessionInputBuffer { + + /** Original session input buffer. */ + private final SessionInputBuffer in; + + /** The wire log to use for writing. */ + private final Wire wire; + + /** + * Create an instance that wraps the specified session input buffer. + * @param in The session input buffer. + * @param wire The wire log to use. + */ + public LoggingSessionInputBuffer(final SessionInputBuffer in, final Wire wire) { + super(); + this.in = in; + this.wire = wire; + } + + public boolean isDataAvailable(int timeout) throws IOException { + return this.in.isDataAvailable(timeout); + } + + public int read(byte[] b, int off, int len) throws IOException { + int l = this.in.read(b, off, len); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, off, l); + } + return l; + } + + public int read() throws IOException { + int l = this.in.read(); + if (this.wire.enabled() && l > 0) { + this.wire.input(l); + } + return l; + } + + public int read(byte[] b) throws IOException { + int l = this.in.read(b); + if (this.wire.enabled() && l > 0) { + this.wire.input(b, 0, l); + } + return l; + } + + public String readLine() throws IOException { + String s = this.in.readLine(); + if (this.wire.enabled() && s != null) { + this.wire.input(s + "[EOL]"); + } + return s; + } + + public int readLine(final CharArrayBuffer buffer) throws IOException { + int l = this.in.readLine(buffer); + if (this.wire.enabled() && l > 0) { + int pos = buffer.length() - l; + String s = new String(buffer.buffer(), pos, l); + this.wire.input(s + "[EOL]"); + } + return l; + } + + public HttpTransportMetrics getMetrics() { + return this.in.getMetrics(); + } + +} diff --git a/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java b/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java new file mode 100644 index 0000000..1afab7d --- /dev/null +++ b/src/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java @@ -0,0 +1,109 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/LoggingSessionOutputBuffer.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; + +import org.apache.http.io.HttpTransportMetrics; +import org.apache.http.io.SessionOutputBuffer; +import org.apache.http.util.CharArrayBuffer; + +/** + * Logs all data written to the wire LOG. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class LoggingSessionOutputBuffer implements SessionOutputBuffer { + + /** Original data transmitter. */ + private final SessionOutputBuffer out; + + /** The wire log to use. */ + private final Wire wire; + + /** + * Create an instance that wraps the specified session output buffer. + * @param out The session output buffer. + * @param wire The Wire log to use. + */ + public LoggingSessionOutputBuffer(final SessionOutputBuffer out, final Wire wire) { + super(); + this.out = out; + this.wire = wire; + } + + public void write(byte[] b, int off, int len) throws IOException { + this.out.write(b, off, len); + if (this.wire.enabled()) { + this.wire.output(b, off, len); + } + } + + public void write(int b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void write(byte[] b) throws IOException { + this.out.write(b); + if (this.wire.enabled()) { + this.wire.output(b); + } + } + + public void flush() throws IOException { + this.out.flush(); + } + + public void writeLine(final CharArrayBuffer buffer) throws IOException { + this.out.writeLine(buffer); + if (this.wire.enabled()) { + String s = new String(buffer.buffer(), 0, buffer.length()); + this.wire.output(s + "[EOL]"); + } + } + + public void writeLine(final String s) throws IOException { + this.out.writeLine(s); + if (this.wire.enabled()) { + this.wire.output(s + "[EOL]"); + } + } + + public HttpTransportMetrics getMetrics() { + return this.out.getMetrics(); + } + +} diff --git a/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java b/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java new file mode 100644 index 0000000..136caf4 --- /dev/null +++ b/src/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java @@ -0,0 +1,290 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/ProxySelectorRoutePlanner.java $ + * $Revision: 658785 $ + * $Date: 2008-05-21 10:47:40 -0700 (Wed, 21 May 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.Proxy; +import java.net.ProxySelector; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.List; + +import org.apache.http.HttpException; +import org.apache.http.HttpHost; +import org.apache.http.HttpRequest; +import org.apache.http.protocol.HttpContext; + +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.HttpRoutePlanner; +import org.apache.http.conn.scheme.Scheme; +import org.apache.http.conn.scheme.SchemeRegistry; + +import org.apache.http.conn.params.ConnRouteParams; + + +/** + * Default implementation of an {@link HttpRoutePlanner}. + * This implementation is based on {@link java.net.ProxySelector}. + * By default, it will pick up the proxy settings of the JVM, either + * from system properties or from the browser running the application. + * Additionally, it interprets some + * {@link org.apache.http.conn.params.ConnRoutePNames parameters}, + * though not the {@link + * org.apache.http.conn.params.ConnRoutePNames#DEFAULT_PROXY DEFAULT_PROXY}. + */ +public class ProxySelectorRoutePlanner implements HttpRoutePlanner { + + /** The scheme registry. */ + protected SchemeRegistry schemeRegistry; + + /** The proxy selector to use, or <code>null</code> for system default. */ + protected ProxySelector proxySelector; + + + /** + * Creates a new proxy selector route planner. + * + * @param schreg the scheme registry + * @param prosel the proxy selector, or + * <code>null</code> for the system default + */ + public ProxySelectorRoutePlanner(SchemeRegistry schreg, + ProxySelector prosel) { + + if (schreg == null) { + throw new IllegalArgumentException + ("SchemeRegistry must not be null."); + } + schemeRegistry = schreg; + proxySelector = prosel; + } + + + /** + * Obtains the proxy selector to use. + * + * @return the proxy selector, or <code>null</code> for the system default + */ + public ProxySelector getProxySelector() { + return this.proxySelector; + } + + + /** + * Sets the proxy selector to use. + * + * @param prosel the proxy selector, or + * <code>null</code> to use the system default + */ + public void setProxySelector(ProxySelector prosel) { + this.proxySelector = prosel; + } + + + + // non-javadoc, see interface HttpRoutePlanner + public HttpRoute determineRoute(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + if (request == null) { + throw new IllegalStateException + ("Request must not be null."); + } + + // If we have a forced route, we can do without a target. + HttpRoute route = + ConnRouteParams.getForcedRoute(request.getParams()); + if (route != null) + return route; + + // If we get here, there is no forced route. + // So we need a target to compute a route. + + if (target == null) { + throw new IllegalStateException + ("Target host must not be null."); + } + + final InetAddress local = + ConnRouteParams.getLocalAddress(request.getParams()); + final HttpHost proxy = determineProxy(target, request, context); + + final Scheme schm = + this.schemeRegistry.getScheme(target.getSchemeName()); + // as it is typically used for TLS/SSL, we assume that + // a layered scheme implies a secure connection + final boolean secure = schm.isLayered(); + + if (proxy == null) { + route = new HttpRoute(target, local, secure); + } else { + route = new HttpRoute(target, local, proxy, secure); + } + return route; + } + + + /** + * Determines a proxy for the given target. + * + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return the proxy to use, or <code>null</code> for a direct route + * + * @throws HttpException + * in case of system proxy settings that cannot be handled + */ + protected HttpHost determineProxy(HttpHost target, + HttpRequest request, + HttpContext context) + throws HttpException { + + // the proxy selector can be 'unset', so we better deal with null here + ProxySelector psel = this.proxySelector; + if (psel == null) + psel = ProxySelector.getDefault(); + if (psel == null) + return null; + + URI targetURI = null; + try { + targetURI = new URI(target.toURI()); + } catch (URISyntaxException usx) { + throw new HttpException + ("Cannot convert host to URI: " + target, usx); + } + List<Proxy> proxies = psel.select(targetURI); + + Proxy p = chooseProxy(proxies, target, request, context); + + HttpHost result = null; + if (p.type() == Proxy.Type.HTTP) { + // convert the socket address to an HttpHost + if (!(p.address() instanceof InetSocketAddress)) { + throw new HttpException + ("Unable to handle non-Inet proxy address: "+p.address()); + } + final InetSocketAddress isa = (InetSocketAddress) p.address(); + // assume default scheme (http) + result = new HttpHost(getHost(isa), isa.getPort()); + } + + return result; + } + + + /** + * Obtains a host from an {@link InetSocketAddress}. + * + * @param isa the socket address + * + * @return a host string, either as a symbolic name or + * as a literal IP address string + * <br/> + * (TODO: determine format for IPv6 addresses, with or without [brackets]) + */ + protected String getHost(InetSocketAddress isa) { + + //@@@ Will this work with literal IPv6 addresses, or do we + //@@@ need to wrap these in [] for the string representation? + //@@@ Having it in this method at least allows for easy workarounds. + return isa.isUnresolved() ? + isa.getHostName() : isa.getAddress().getHostAddress(); + + } + + + /* + * Chooses a proxy from a list of available proxies. + * The default implementation just picks the first non-SOCKS proxy + * from the list. If there are only SOCKS proxies, + * {@link Proxy#NO_PROXY Proxy.NO_PROXY} is returned. + * Derived classes may implement more advanced strategies, + * such as proxy rotation if there are multiple options. + * + * @param proxies the list of proxies to choose from, + * never <code>null</code> or empty + * @param target the planned target, never <code>null</code> + * @param request the request to be sent, never <code>null</code> + * @param context the context, or <code>null</code> + * + * @return a proxy of type {@link Proxy.Type#DIRECT DIRECT} + * or {@link Proxy.Type#HTTP HTTP}, never <code>null</code> + */ + protected Proxy chooseProxy(List<Proxy> proxies, + HttpHost target, + HttpRequest request, + HttpContext context) { + + if ((proxies == null) || proxies.isEmpty()) { + throw new IllegalArgumentException + ("Proxy list must not be empty."); + } + + Proxy result = null; + + // check the list for one we can use + for (int i=0; (result == null) && (i < proxies.size()); i++) { + + Proxy p = proxies.get(i); + switch (p.type()) { + + case DIRECT: + case HTTP: + result = p; + break; + + case SOCKS: + // SOCKS hosts are not handled on the route level. + // The socket may make use of the SOCKS host though. + break; + } + } + + if (result == null) { + //@@@ log as warning or info that only a socks proxy is available? + // result can only be null if all proxies are socks proxies + // socks proxies are not handled on the route planning level + result = Proxy.NO_PROXY; + } + + return result; + } + +} // class ProxySelectorRoutePlanner + diff --git a/src/org/apache/http/impl/conn/SingleClientConnManager.java b/src/org/apache/http/impl/conn/SingleClientConnManager.java new file mode 100644 index 0000000..7999f3e --- /dev/null +++ b/src/org/apache/http/impl/conn/SingleClientConnManager.java @@ -0,0 +1,444 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/SingleClientConnManager.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.routing.RouteTracker; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.params.HttpParams; + + +/** + * A connection "manager" for a single connection. + * This manager is good only for single-threaded use. + * Allocation <i>always</i> returns the connection immediately, + * even if it has not been released after the previous allocation. + * In that case, a {@link #MISUSE_MESSAGE warning} is logged + * and the previously issued connection is revoked. + * <p> + * This class is derived from <code>SimpleHttpConnectionManager</code> + * in HttpClient 3. See there for original authors. + * </p> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ + * + * @since 4.0 + */ +public class SingleClientConnManager implements ClientConnectionManager { + + private final Log log = LogFactory.getLog(getClass()); + + /** The message to be logged on multiple allocation. */ + public final static String MISUSE_MESSAGE = + "Invalid use of SingleClientConnManager: connection still allocated.\n" + + "Make sure to release the connection before allocating another one."; + + + /** The schemes supported by this connection manager. */ + protected SchemeRegistry schemeRegistry; + + /** The operator for opening and updating connections. */ + protected ClientConnectionOperator connOperator; + + /** The one and only entry in this pool. */ + protected PoolEntry uniquePoolEntry; + + /** The currently issued managed connection, if any. */ + protected ConnAdapter managedConn; + + /** The time of the last connection release, or -1. */ + protected long lastReleaseTime; + + /** The time the last released connection expires and shouldn't be reused. */ + protected long connectionExpiresTime; + + /** Whether the connection should be shut down on release. */ + protected boolean alwaysShutDown; + + /** Indicates whether this connection manager is shut down. */ + protected volatile boolean isShutDown; + + + + + /** + * Creates a new simple connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry, or + * <code>null</code> for the default registry + */ + public SingleClientConnManager(HttpParams params, + SchemeRegistry schreg) { + + if (schreg == null) { + throw new IllegalArgumentException + ("Scheme registry must not be null."); + } + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.uniquePoolEntry = new PoolEntry(); + this.managedConn = null; + this.lastReleaseTime = -1L; + this.alwaysShutDown = false; //@@@ from params? as argument? + this.isShutDown = false; + + } // <constructor> + + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + + // non-javadoc, see interface ClientConnectionManager + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg); + } + + + /** + * Asserts that this manager is not shut down. + * + * @throws IllegalStateException if this manager is shut down + */ + protected final void assertStillUp() + throws IllegalStateException { + + if (this.isShutDown) + throw new IllegalStateException("Manager is shut down."); + } + + + public final ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + return new ClientConnectionRequest() { + + public void abortRequest() { + // Nothing to abort, since requests are immediate. + } + + public ManagedClientConnection getConnection( + long timeout, TimeUnit tunit) { + return SingleClientConnManager.this.getConnection( + route, state); + } + + }; + } + + + /** + * Obtains a connection. + * This method does not block. + * + * @param route where the connection should point to + * + * @return a connection that can be used to communicate + * along the given route + */ + public ManagedClientConnection getConnection(HttpRoute route, Object state) { + + if (route == null) { + throw new IllegalArgumentException("Route may not be null."); + } + assertStillUp(); + + if (log.isDebugEnabled()) { + log.debug("Get connection for route " + route); + } + + if (managedConn != null) + revokeConnection(); + + // check re-usability of the connection + boolean recreate = false; + boolean shutdown = false; + + // Kill the connection if it expired. + closeExpiredConnections(); + + if (uniquePoolEntry.connection.isOpen()) { + RouteTracker tracker = uniquePoolEntry.tracker; + shutdown = (tracker == null || // can happen if method is aborted + !tracker.toRoute().equals(route)); + } else { + // If the connection is not open, create a new PoolEntry, + // as the connection may have been marked not reusable, + // due to aborts -- and the PoolEntry should not be reused + // either. There's no harm in recreating an entry if + // the connection is closed. + recreate = true; + } + + if (shutdown) { + recreate = true; + try { + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + log.debug("Problem shutting down connection.", iox); + } + } + + if (recreate) + uniquePoolEntry = new PoolEntry(); + + managedConn = new ConnAdapter(uniquePoolEntry, route); + + return managedConn; + } + + + // non-javadoc, see interface ClientConnectionManager + public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { + assertStillUp(); + + if (!(conn instanceof ConnAdapter)) { + throw new IllegalArgumentException + ("Connection class mismatch, " + + "connection not obtained from this manager."); + } + + if (log.isDebugEnabled()) { + log.debug("Releasing connection " + conn); + } + + ConnAdapter sca = (ConnAdapter) conn; + if (sca.poolEntry == null) + return; // already released + ClientConnectionManager manager = sca.getManager(); + if (manager != null && manager != this) { + throw new IllegalArgumentException + ("Connection not obtained from this manager."); + } + + try { + // make sure that the response has been read completely + if (sca.isOpen() && (this.alwaysShutDown || + !sca.isMarkedReusable()) + ) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not reusable."); + } + + // make sure this connection will not be re-used + // we might have gotten here because of a shutdown trigger + // shutdown of the adapter also clears the tracked route + sca.shutdown(); + } + } catch (IOException iox) { + //@@@ log as warning? let pass? + if (log.isDebugEnabled()) + log.debug("Exception shutting down released connection.", + iox); + } finally { + sca.detach(); + managedConn = null; + lastReleaseTime = System.currentTimeMillis(); + if(validDuration > 0) + connectionExpiresTime = timeUnit.toMillis(validDuration) + lastReleaseTime; + else + connectionExpiresTime = Long.MAX_VALUE; + } + } // releaseConnection + + public void closeExpiredConnections() { + if(System.currentTimeMillis() >= connectionExpiresTime) { + closeIdleConnections(0, TimeUnit.MILLISECONDS); + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void closeIdleConnections(long idletime, TimeUnit tunit) { + assertStillUp(); + + // idletime can be 0 or negative, no problem there + if (tunit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + + if ((managedConn == null) && uniquePoolEntry.connection.isOpen()) { + final long cutoff = + System.currentTimeMillis() - tunit.toMillis(idletime); + if (lastReleaseTime <= cutoff) { + try { + uniquePoolEntry.close(); + } catch (IOException iox) { + // ignore + log.debug("Problem closing idle connection.", iox); + } + } + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void shutdown() { + + this.isShutDown = true; + + if (managedConn != null) + managedConn.detach(); + + try { + if (uniquePoolEntry != null) // and connection open? + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + // ignore + log.debug("Problem while shutting down manager.", iox); + } finally { + uniquePoolEntry = null; + } + } + + + /** + * Revokes the currently issued connection. + * The adapter gets disconnected, the connection will be shut down. + */ + protected void revokeConnection() { + if (managedConn == null) + return; + + log.warn(MISUSE_MESSAGE); + + managedConn.detach(); + + try { + uniquePoolEntry.shutdown(); + } catch (IOException iox) { + // ignore + log.debug("Problem while shutting down connection.", iox); + } + } + + + /** + * The pool entry for this connection manager. + */ + protected class PoolEntry extends AbstractPoolEntry { + + /** + * Creates a new pool entry. + * + */ + protected PoolEntry() { + super(SingleClientConnManager.this.connOperator, null); + } + + /** + * Closes the connection in this pool entry. + */ + protected void close() + throws IOException { + + shutdownEntry(); + if (connection.isOpen()) + connection.close(); + } + + + /** + * Shuts down the connection in this pool entry. + */ + protected void shutdown() + throws IOException { + + shutdownEntry(); + if (connection.isOpen()) + connection.shutdown(); + } + + } // class PoolEntry + + + + /** + * The connection adapter used by this manager. + */ + protected class ConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new connection adapter. + * + * @param entry the pool entry for the connection being wrapped + * @param route the planned route for this connection + */ + protected ConnAdapter(PoolEntry entry, HttpRoute route) { + super(SingleClientConnManager.this, entry); + markReusable(); + entry.route = route; + } + + } + + +} // class SingleClientConnManager diff --git a/src/org/apache/http/impl/conn/Wire.java b/src/org/apache/http/impl/conn/Wire.java new file mode 100644 index 0000000..147b7f5 --- /dev/null +++ b/src/org/apache/http/impl/conn/Wire.java @@ -0,0 +1,160 @@ +/* + * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/Wire.java,v 1.9 2004/06/24 21:39:52 mbecke Exp $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn; + +import java.io.IOException; +import java.io.InputStream; +import java.io.ByteArrayInputStream; +import org.apache.commons.logging.Log; + +/** + * Logs data to the wire LOG. + * + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + */ +public class Wire { + + private final Log log; + + public Wire(Log log) { + this.log = log; + } + + private void wire(String header, InputStream instream) + throws IOException { + StringBuilder buffer = new StringBuilder(); + int ch; + while ((ch = instream.read()) != -1) { + if (ch == 13) { + buffer.append("[\\r]"); + } else if (ch == 10) { + buffer.append("[\\n]\""); + buffer.insert(0, "\""); + buffer.insert(0, header); + log.debug(buffer.toString()); + buffer.setLength(0); + } else if ((ch < 32) || (ch > 127)) { + buffer.append("[0x"); + buffer.append(Integer.toHexString(ch)); + buffer.append("]"); + } else { + buffer.append((char) ch); + } + } + if (buffer.length() > 0) { + buffer.append('\"'); + buffer.insert(0, '\"'); + buffer.insert(0, header); + log.debug(buffer.toString()); + } + } + + + public boolean enabled() { + return log.isDebugEnabled(); + } + + public void output(InputStream outstream) + throws IOException { + if (outstream == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", outstream); + } + + public void input(InputStream instream) + throws IOException { + if (instream == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", instream); + } + + public void output(byte[] b, int off, int len) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", new ByteArrayInputStream(b, off, len)); + } + + public void input(byte[] b, int off, int len) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", new ByteArrayInputStream(b, off, len)); + } + + public void output(byte[] b) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Output may not be null"); + } + wire(">> ", new ByteArrayInputStream(b)); + } + + public void input(byte[] b) + throws IOException { + if (b == null) { + throw new IllegalArgumentException("Input may not be null"); + } + wire("<< ", new ByteArrayInputStream(b)); + } + + public void output(int b) + throws IOException { + output(new byte[] {(byte) b}); + } + + public void input(int b) + throws IOException { + input(new byte[] {(byte) b}); + } + + public void output(final String s) + throws IOException { + if (s == null) { + throw new IllegalArgumentException("Output may not be null"); + } + output(s.getBytes()); + } + + public void input(final String s) + throws IOException { + if (s == null) { + throw new IllegalArgumentException("Input may not be null"); + } + input(s.getBytes()); + } +} diff --git a/src/org/apache/http/impl/conn/package.html b/src/org/apache/http/impl/conn/package.html new file mode 100644 index 0000000..54eb3c2 --- /dev/null +++ b/src/org/apache/http/impl/conn/package.html @@ -0,0 +1,5 @@ +<body> + + +</body> + diff --git a/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java new file mode 100644 index 0000000..2b37d72 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/AbstractConnPool.java @@ -0,0 +1,332 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/AbstractConnPool.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.conn.IdleConnectionHandler; + + +/** + * An abstract connection pool. + * It is used by the {@link ThreadSafeClientConnManager}. + * The abstract pool includes a {@link #poolLock}, which is used to + * synchronize access to the internal pool datastructures. + * Don't use <code>synchronized</code> for that purpose! + */ +public abstract class AbstractConnPool implements RefQueueHandler { + + private final Log log = LogFactory.getLog(getClass()); + + /** + * The global lock for this pool. + */ + protected final Lock poolLock; + + + /** + * References to issued connections. + * Objects in this set are of class + * {@link BasicPoolEntryRef BasicPoolEntryRef}, + * and point to the pool entry for the issued connection. + * GCed connections are detected by the missing pool entries. + */ + protected Set<BasicPoolEntryRef> issuedConnections; + + /** The handler for idle connections. */ + protected IdleConnectionHandler idleConnHandler; + + /** The current total number of connections. */ + protected int numConnections; + + /** + * A reference queue to track loss of pool entries to GC. + * The same queue is used to track loss of the connection manager, + * so we cannot specialize the type. + */ + protected ReferenceQueue<Object> refQueue; + + /** A worker (thread) to track loss of pool entries to GC. */ + private RefQueueWorker refWorker; + + + /** Indicates whether this pool is shut down. */ + protected volatile boolean isShutDown; + + /** + * Creates a new connection pool. + */ + protected AbstractConnPool() { + issuedConnections = new HashSet<BasicPoolEntryRef>(); + idleConnHandler = new IdleConnectionHandler(); + + boolean fair = false; //@@@ check parameters to decide + poolLock = new ReentrantLock(fair); + } + + + /** + * Enables connection garbage collection (GC). + * This method must be called immediately after creating the + * connection pool. It is not possible to enable connection GC + * after pool entries have been created. Neither is it possible + * to disable connection GC. + * + * @throws IllegalStateException + * if connection GC is already enabled, or if it cannot be + * enabled because there already are pool entries + */ + public void enableConnectionGC() + throws IllegalStateException { + + if (refQueue != null) { + throw new IllegalStateException("Connection GC already enabled."); + } + poolLock.lock(); + try { + if (numConnections > 0) { //@@@ is this check sufficient? + throw new IllegalStateException("Pool already in use."); + } + } finally { + poolLock.unlock(); + } + + refQueue = new ReferenceQueue<Object>(); + refWorker = new RefQueueWorker(refQueue, this); + Thread t = new Thread(refWorker); //@@@ use a thread factory + t.setDaemon(true); + t.setName("RefQueueWorker@" + this); + t.start(); + } + + + /** + * Obtains a pool entry with a connection within the given timeout. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + public final + BasicPoolEntry getEntry( + HttpRoute route, + Object state, + long timeout, + TimeUnit tunit) + throws ConnectionPoolTimeoutException, InterruptedException { + return requestPoolEntry(route, state).getPoolEntry(timeout, tunit); + } + + /** + * Returns a new {@link PoolEntryRequest}, from which a {@link BasicPoolEntry} + * can be obtained, or the request can be aborted. + */ + public abstract PoolEntryRequest requestPoolEntry(HttpRoute route, Object state); + + + /** + * Returns an entry into the pool. + * The connection of the entry is expected to be in a suitable state, + * either open and re-usable, or closed. The pool will not make any + * attempt to determine whether it can be re-used or not. + * + * @param entry the entry for the connection to release + * @param reusable <code>true</code> if the entry is deemed + * reusable, <code>false</code> otherwise. + * @param validDuration The duration that the entry should remain free and reusable. + * @param timeUnit The unit of time the duration is measured in. + */ + public abstract void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) + ; + + + + // non-javadoc, see interface RefQueueHandler +// BEGIN android-changed + public void handleReference(Reference ref) { +// END android-changed + poolLock.lock(); + try { + + if (ref instanceof BasicPoolEntryRef) { + // check if the GCed pool entry was still in use + //@@@ find a way to detect this without lookup + //@@@ flag in the BasicPoolEntryRef, to be reset when freed? + final boolean lost = issuedConnections.remove(ref); + if (lost) { + final HttpRoute route = + ((BasicPoolEntryRef)ref).getRoute(); + if (log.isDebugEnabled()) { + log.debug("Connection garbage collected. " + route); + } + handleLostEntry(route); + } + } + + } finally { + poolLock.unlock(); + } + } + + + /** + * Handles cleaning up for a lost pool entry with the given route. + * A lost pool entry corresponds to a connection that was + * garbage collected instead of being properly released. + * + * @param route the route of the pool entry that was lost + */ + protected abstract void handleLostEntry(HttpRoute route) + ; + + + /** + * Closes idle connections. + * + * @param idletime the time the connections should have been idle + * in order to be closed now + * @param tunit the unit for the <code>idletime</code> + */ + public void closeIdleConnections(long idletime, TimeUnit tunit) { + + // idletime can be 0 or negative, no problem there + if (tunit == null) { + throw new IllegalArgumentException("Time unit must not be null."); + } + + poolLock.lock(); + try { + idleConnHandler.closeIdleConnections(tunit.toMillis(idletime)); + } finally { + poolLock.unlock(); + } + } + + public void closeExpiredConnections() { + poolLock.lock(); + try { + idleConnHandler.closeExpiredConnections(); + } finally { + poolLock.unlock(); + } + } + + + //@@@ revise this cleanup stuff (closeIdle+deleteClosed), it's not good + + /** + * Deletes all entries for closed connections. + */ + public abstract void deleteClosedConnections() + ; + + + /** + * Shuts down this pool and all associated resources. + * Overriding methods MUST call the implementation here! + */ + public void shutdown() { + + poolLock.lock(); + try { + + if (isShutDown) + return; + + // no point in monitoring GC anymore + if (refWorker != null) + refWorker.shutdown(); + + // close all connections that are issued to an application + Iterator<BasicPoolEntryRef> iter = issuedConnections.iterator(); + while (iter.hasNext()) { + BasicPoolEntryRef per = iter.next(); + iter.remove(); + BasicPoolEntry entry = per.get(); + if (entry != null) { + closeConnection(entry.getConnection()); + } + } + + // remove all references to connections + //@@@ use this for shutting them down instead? + idleConnHandler.removeAll(); + + isShutDown = true; + + } finally { + poolLock.unlock(); + } + } + + + /** + * Closes a connection from this pool. + * + * @param conn the connection to close, or <code>null</code> + */ + protected void closeConnection(final OperatedClientConnection conn) { + if (conn != null) { + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + } + } + + + + + +} // class AbstractConnPool + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java new file mode 100644 index 0000000..dded360 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java @@ -0,0 +1,88 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntry.java $ + * $Revision: 652721 $ + * $Date: 2008-05-01 17:32:20 -0700 (Thu, 01 May 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.lang.ref.ReferenceQueue; + +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.impl.conn.AbstractPoolEntry; + + + +/** + * Basic implementation of a connection pool entry. + */ +public class BasicPoolEntry extends AbstractPoolEntry { + + /** + * A weak reference to <code>this</code> used to detect GC of entries. + * Pool entries can only be GCed when they are allocated by an application + * and therefore not referenced with a hard link in the manager. + */ + private final BasicPoolEntryRef reference; + + /** + * Creates a new pool entry. + * + * @param op the connection operator + * @param route the planned route for the connection + * @param queue the reference queue for tracking GC of this entry, + * or <code>null</code> + */ + public BasicPoolEntry(ClientConnectionOperator op, + HttpRoute route, + ReferenceQueue<Object> queue) { + super(op, route); + if (route == null) { + throw new IllegalArgumentException("HTTP route may not be null"); + } + this.reference = new BasicPoolEntryRef(this, queue); + } + + protected final OperatedClientConnection getConnection() { + return super.connection; + } + + protected final HttpRoute getPlannedRoute() { + return super.route; + } + + protected final BasicPoolEntryRef getWeakRef() { + return this.reference; + } + + +} // class BasicPoolEntry + + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java new file mode 100644 index 0000000..32df8a5 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java @@ -0,0 +1,80 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPoolEntryRef.java $ + * $Revision: 674186 $ + * $Date: 2008-07-05 05:18:54 -0700 (Sat, 05 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.lang.ref.WeakReference; +import java.lang.ref.ReferenceQueue; + +import org.apache.http.conn.routing.HttpRoute; + + + +/** + * A weak reference to a {@link BasicPoolEntry BasicPoolEntry}. + * This reference explicitly keeps the planned route, so the connection + * can be reclaimed if it is lost to garbage collection. + */ +public class BasicPoolEntryRef extends WeakReference<BasicPoolEntry> { + + /** The planned route of the entry. */ + private final HttpRoute route; + + + /** + * Creates a new reference to a pool entry. + * + * @param entry the pool entry, must not be <code>null</code> + * @param queue the reference queue, or <code>null</code> + */ + public BasicPoolEntryRef(BasicPoolEntry entry, + ReferenceQueue<Object> queue) { + super(entry, queue); + if (entry == null) { + throw new IllegalArgumentException + ("Pool entry must not be null."); + } + route = entry.getPlannedRoute(); + } + + + /** + * Obtain the planned route for the referenced entry. + * The planned route is still available, even if the entry is gone. + * + * @return the planned route + */ + public final HttpRoute getRoute() { + return this.route; + } + +} // class BasicPoolEntryRef + diff --git a/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java new file mode 100644 index 0000000..29455d0 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java @@ -0,0 +1,83 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/BasicPooledConnAdapter.java $ + * $Revision: 653214 $ + * $Date: 2008-05-04 07:12:13 -0700 (Sun, 04 May 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.impl.conn.AbstractPoolEntry; +import org.apache.http.impl.conn.AbstractPooledConnAdapter; + + + +/** + * A connection wrapper and callback handler. + * All connections given out by the manager are wrappers which + * can be {@link #detach detach}ed to prevent further use on release. + */ +public class BasicPooledConnAdapter extends AbstractPooledConnAdapter { + + /** + * Creates a new adapter. + * + * @param tsccm the connection manager + * @param entry the pool entry for the connection being wrapped + */ + protected BasicPooledConnAdapter(ThreadSafeClientConnManager tsccm, + AbstractPoolEntry entry) { + super(tsccm, entry); + markReusable(); + } + + + @Override + protected ClientConnectionManager getManager() { + // override needed only to make method visible in this package + return super.getManager(); + } + + + /** + * Obtains the pool entry. + * + * @return the pool entry, or <code>null</code> if detached + */ + protected AbstractPoolEntry getPoolEntry() { + return super.poolEntry; + } + + + // non-javadoc, see base class + @Override + protected void detach() { + // override needed only to make method visible in this package + super.detach(); + } +} diff --git a/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java new file mode 100644 index 0000000..cf59129 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java @@ -0,0 +1,698 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ConnPoolByRoute.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.util.Date; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Queue; +import java.util.LinkedList; +import java.util.Map; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.params.ConnPerRoute; +import org.apache.http.conn.params.ConnManagerParams; +import org.apache.http.params.HttpParams; + + +/** + * A connection pool that maintains connections by route. + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3.x, see there for original authors. It implements the same + * algorithm for connection re-use and connection-per-host enforcement: + * <ul> + * <li>connections are re-used only for the exact same route</li> + * <li>connection limits are enforced per route rather than per host</li> + * </ul> + * Note that access to the pool datastructures is synchronized via the + * {@link AbstractConnPool#poolLock poolLock} in the base class, + * not via <code>synchronized</code> methods. + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * @author and others + */ +public class ConnPoolByRoute extends AbstractConnPool { + + private final Log log = LogFactory.getLog(getClass()); + + /** Connection operator for this pool */ + protected final ClientConnectionOperator operator; + + /** The list of free connections */ + protected Queue<BasicPoolEntry> freeConnections; + + /** The list of WaitingThreads waiting for a connection */ + protected Queue<WaitingThread> waitingThreads; + + /** + * A map of route-specific pools. + * Keys are of class {@link HttpRoute}, + * values of class {@link RouteSpecificPool}. + */ + protected final Map<HttpRoute, RouteSpecificPool> routeToPool; + + protected final int maxTotalConnections; + + private final ConnPerRoute connPerRoute; + + /** + * Creates a new connection pool, managed by route. + */ + public ConnPoolByRoute(final ClientConnectionOperator operator, final HttpParams params) { + super(); + if (operator == null) { + throw new IllegalArgumentException("Connection operator may not be null"); + } + this.operator = operator; + + freeConnections = createFreeConnQueue(); + waitingThreads = createWaitingThreadQueue(); + routeToPool = createRouteToPoolMap(); + maxTotalConnections = ConnManagerParams + .getMaxTotalConnections(params); + connPerRoute = ConnManagerParams + .getMaxConnectionsPerRoute(params); + } + + + /** + * Creates the queue for {@link #freeConnections}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<BasicPoolEntry> createFreeConnQueue() { + return new LinkedList<BasicPoolEntry>(); + } + + /** + * Creates the queue for {@link #waitingThreads}. + * Called once by the constructor. + * + * @return a queue + */ + protected Queue<WaitingThread> createWaitingThreadQueue() { + return new LinkedList<WaitingThread>(); + } + + /** + * Creates the map for {@link #routeToPool}. + * Called once by the constructor. + * + * @return a map + */ + protected Map<HttpRoute, RouteSpecificPool> createRouteToPoolMap() { + return new HashMap<HttpRoute, RouteSpecificPool>(); + } + + + /** + * Creates a new route-specific pool. + * Called by {@link #getRoutePool} when necessary. + * + * @param route the route + * + * @return the new pool + */ + protected RouteSpecificPool newRouteSpecificPool(HttpRoute route) { + return new RouteSpecificPool(route, connPerRoute.getMaxForRoute(route)); + } + + + /** + * Creates a new waiting thread. + * Called by {@link #getRoutePool} when necessary. + * + * @param cond the condition to wait for + * @param rospl the route specific pool, or <code>null</code> + * + * @return a waiting thread representation + */ + protected WaitingThread newWaitingThread(Condition cond, + RouteSpecificPool rospl) { + return new WaitingThread(cond, rospl); + } + + + /** + * Get a route-specific pool of available connections. + * + * @param route the route + * @param create whether to create the pool if it doesn't exist + * + * @return the pool for the argument route, + * never <code>null</code> if <code>create</code> is <code>true</code> + */ + protected RouteSpecificPool getRoutePool(HttpRoute route, + boolean create) { + RouteSpecificPool rospl = null; + poolLock.lock(); + try { + + rospl = routeToPool.get(route); + if ((rospl == null) && create) { + // no pool for this route yet (or anymore) + rospl = newRouteSpecificPool(route); + routeToPool.put(route, rospl); + } + + } finally { + poolLock.unlock(); + } + + return rospl; + } + + + //@@@ consider alternatives for gathering statistics + public int getConnectionsInPool(HttpRoute route) { + + poolLock.lock(); + try { + // don't allow a pool to be created here! + RouteSpecificPool rospl = getRoutePool(route, false); + return (rospl != null) ? rospl.getEntryCount() : 0; + + } finally { + poolLock.unlock(); + } + } + + @Override + public PoolEntryRequest requestPoolEntry( + final HttpRoute route, + final Object state) { + + final WaitingThreadAborter aborter = new WaitingThreadAborter(); + + return new PoolEntryRequest() { + + public void abortRequest() { + poolLock.lock(); + try { + aborter.abort(); + } finally { + poolLock.unlock(); + } + } + + public BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) + throws InterruptedException, ConnectionPoolTimeoutException { + return getEntryBlocking(route, state, timeout, tunit, aborter); + } + + }; + } + + /** + * Obtains a pool entry with a connection within the given timeout. + * If a {@link WaitingThread} is used to block, {@link WaitingThreadAborter#setWaitingThread(WaitingThread)} + * must be called before blocking, to allow the thread to be interrupted. + * + * @param route the route for which to get the connection + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * @param aborter an object which can abort a {@link WaitingThread}. + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted + */ + protected BasicPoolEntry getEntryBlocking( + HttpRoute route, Object state, + long timeout, TimeUnit tunit, + WaitingThreadAborter aborter) + throws ConnectionPoolTimeoutException, InterruptedException { + + Date deadline = null; + if (timeout > 0) { + deadline = new Date + (System.currentTimeMillis() + tunit.toMillis(timeout)); + } + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + WaitingThread waitingThread = null; + + while (entry == null) { + + if (isShutDown) { + throw new IllegalStateException + ("Connection pool shut down."); + } + + if (log.isDebugEnabled()) { + log.debug("Total connections kept alive: " + freeConnections.size()); + log.debug("Total issued connections: " + issuedConnections.size()); + log.debug("Total allocated connection: " + numConnections + " out of " + maxTotalConnections); + } + + // the cases to check for: + // - have a free connection for that route + // - allowed to create a free connection for that route + // - can delete and replace a free connection for another route + // - need to wait for one of the things above to come true + + entry = getFreeEntry(rospl, state); + if (entry != null) { + break; + } + + boolean hasCapacity = rospl.getCapacity() > 0; + + if (log.isDebugEnabled()) { + log.debug("Available capacity: " + rospl.getCapacity() + + " out of " + rospl.getMaxEntries() + + " [" + route + "][" + state + "]"); + } + + if (hasCapacity && numConnections < maxTotalConnections) { + + entry = createEntry(rospl, operator); + + } else if (hasCapacity && !freeConnections.isEmpty()) { + + deleteLeastUsedEntry(); + entry = createEntry(rospl, operator); + + } else { + + if (log.isDebugEnabled()) { + log.debug("Need to wait for connection" + + " [" + route + "][" + state + "]"); + } + + if (waitingThread == null) { + waitingThread = + newWaitingThread(poolLock.newCondition(), rospl); + aborter.setWaitingThread(waitingThread); + } + + boolean success = false; + try { + rospl.queueThread(waitingThread); + waitingThreads.add(waitingThread); + success = waitingThread.await(deadline); + + } finally { + // In case of 'success', we were woken up by the + // connection pool and should now have a connection + // waiting for us, or else we're shutting down. + // Just continue in the loop, both cases are checked. + rospl.removeThread(waitingThread); + waitingThreads.remove(waitingThread); + } + + // check for spurious wakeup vs. timeout + if (!success && (deadline != null) && + (deadline.getTime() <= System.currentTimeMillis())) { + throw new ConnectionPoolTimeoutException + ("Timeout waiting for connection"); + } + } + } // while no entry + + } finally { + poolLock.unlock(); + } + + return entry; + + } // getEntry + + + // non-javadoc, see base class AbstractConnPool + @Override + public void freeEntry(BasicPoolEntry entry, boolean reusable, long validDuration, TimeUnit timeUnit) { + + HttpRoute route = entry.getPlannedRoute(); + if (log.isDebugEnabled()) { + log.debug("Freeing connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + if (isShutDown) { + // the pool is shut down, release the + // connection's resources and get out of here + closeConnection(entry.getConnection()); + return; + } + + // no longer issued, we keep a hard reference now + issuedConnections.remove(entry.getWeakRef()); + + RouteSpecificPool rospl = getRoutePool(route, true); + + if (reusable) { + rospl.freeEntry(entry); + freeConnections.add(entry); + idleConnHandler.add(entry.getConnection(), validDuration, timeUnit); + } else { + rospl.dropEntry(); + numConnections--; + } + + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + + } // freeEntry + + + + /** + * If available, get a free pool entry for a route. + * + * @param rospl the route-specific pool from which to get an entry + * + * @return an available pool entry for the given route, or + * <code>null</code> if none is available + */ + protected BasicPoolEntry getFreeEntry(RouteSpecificPool rospl, Object state) { + + BasicPoolEntry entry = null; + poolLock.lock(); + try { + boolean done = false; + while(!done) { + + entry = rospl.allocEntry(state); + + if (entry != null) { + if (log.isDebugEnabled()) { + log.debug("Getting free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + + } + freeConnections.remove(entry); + boolean valid = idleConnHandler.remove(entry.getConnection()); + if(!valid) { + // If the free entry isn't valid anymore, get rid of it + // and loop to find another one that might be valid. + if(log.isDebugEnabled()) + log.debug("Closing expired free connection" + + " [" + rospl.getRoute() + "][" + state + "]"); + closeConnection(entry.getConnection()); + // We use dropEntry instead of deleteEntry because the entry + // is no longer "free" (we just allocated it), and deleteEntry + // can only be used to delete free entries. + rospl.dropEntry(); + numConnections--; + } else { + issuedConnections.add(entry.getWeakRef()); + done = true; + } + + } else { + done = true; + if (log.isDebugEnabled()) { + log.debug("No free connections" + + " [" + rospl.getRoute() + "][" + state + "]"); + } + } + } + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Creates a new pool entry. + * This method assumes that the new connection will be handed + * out immediately. + * + * @param rospl the route-specific pool for which to create the entry + * @param op the operator for creating a connection + * + * @return the new pool entry for a new connection + */ + protected BasicPoolEntry createEntry(RouteSpecificPool rospl, + ClientConnectionOperator op) { + + if (log.isDebugEnabled()) { + log.debug("Creating new connection [" + rospl.getRoute() + "]"); + } + + // the entry will create the connection when needed + BasicPoolEntry entry = + new BasicPoolEntry(op, rospl.getRoute(), refQueue); + + poolLock.lock(); + try { + + rospl.createdEntry(entry); + numConnections++; + + issuedConnections.add(entry.getWeakRef()); + + } finally { + poolLock.unlock(); + } + + return entry; + } + + + /** + * Deletes a given pool entry. + * This closes the pooled connection and removes all references, + * so that it can be GCed. + * + * <p><b>Note:</b> Does not remove the entry from the freeConnections list. + * It is assumed that the caller has already handled this step.</p> + * <!-- @@@ is that a good idea? or rather fix it? --> + * + * @param entry the pool entry for the connection to delete + */ + protected void deleteEntry(BasicPoolEntry entry) { + + HttpRoute route = entry.getPlannedRoute(); + + if (log.isDebugEnabled()) { + log.debug("Deleting connection" + + " [" + route + "][" + entry.getState() + "]"); + } + + poolLock.lock(); + try { + + closeConnection(entry.getConnection()); + + RouteSpecificPool rospl = getRoutePool(route, true); + rospl.deleteEntry(entry); + numConnections--; + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + idleConnHandler.remove(entry.getConnection());// not idle, but dead + + } finally { + poolLock.unlock(); + } + } + + + /** + * Delete an old, free pool entry to make room for a new one. + * Used to replace pool entries with ones for a different route. + */ + protected void deleteLeastUsedEntry() { + + try { + poolLock.lock(); + + //@@@ with get() instead of remove, we could + //@@@ leave the removing to deleteEntry() + BasicPoolEntry entry = freeConnections.remove(); + + if (entry != null) { + deleteEntry(entry); + } else if (log.isDebugEnabled()) { + log.debug("No free connection to delete."); + } + + } finally { + poolLock.unlock(); + } + } + + + // non-javadoc, see base class AbstractConnPool + @Override + protected void handleLostEntry(HttpRoute route) { + + poolLock.lock(); + try { + + RouteSpecificPool rospl = getRoutePool(route, true); + rospl.dropEntry(); + if (rospl.isUnused()) { + routeToPool.remove(route); + } + + numConnections--; + notifyWaitingThread(rospl); + + } finally { + poolLock.unlock(); + } + } + + + /** + * Notifies a waiting thread that a connection is available. + * This will wake a thread waiting in the specific route pool, + * if there is one. + * Otherwise, a thread in the connection pool will be notified. + * + * @param rospl the pool in which to notify, or <code>null</code> + */ + protected void notifyWaitingThread(RouteSpecificPool rospl) { + + //@@@ while this strategy provides for best connection re-use, + //@@@ is it fair? only do this if the connection is open? + // Find the thread we are going to notify. We want to ensure that + // each waiting thread is only interrupted once, so we will remove + // it from all wait queues before interrupting. + WaitingThread waitingThread = null; + + poolLock.lock(); + try { + + if ((rospl != null) && rospl.hasThread()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on pool" + + " [" + rospl.getRoute() + "]"); + } + waitingThread = rospl.nextThread(); + } else if (!waitingThreads.isEmpty()) { + if (log.isDebugEnabled()) { + log.debug("Notifying thread waiting on any pool"); + } + waitingThread = waitingThreads.remove(); + } else if (log.isDebugEnabled()) { + log.debug("Notifying no-one, there are no waiting threads"); + } + + if (waitingThread != null) { + waitingThread.wakeup(); + } + + } finally { + poolLock.unlock(); + } + } + + + //@@@ revise this cleanup stuff + //@@@ move method to base class when deleteEntry() is fixed + // non-javadoc, see base class AbstractConnPool + @Override + public void deleteClosedConnections() { + + poolLock.lock(); + try { + + Iterator<BasicPoolEntry> iter = freeConnections.iterator(); + while (iter.hasNext()) { + BasicPoolEntry entry = iter.next(); + if (!entry.getConnection().isOpen()) { + iter.remove(); + deleteEntry(entry); + } + } + + } finally { + poolLock.unlock(); + } + } + + + // non-javadoc, see base class AbstractConnPool + @Override + public void shutdown() { + + poolLock.lock(); + try { + + super.shutdown(); + + // close all free connections + //@@@ move this to base class? + Iterator<BasicPoolEntry> ibpe = freeConnections.iterator(); + while (ibpe.hasNext()) { + BasicPoolEntry entry = ibpe.next(); + ibpe.remove(); + closeConnection(entry.getConnection()); + } + + // wake up all waiting threads + Iterator<WaitingThread> iwth = waitingThreads.iterator(); + while (iwth.hasNext()) { + WaitingThread waiter = iwth.next(); + iwth.remove(); + waiter.wakeup(); + } + + routeToPool.clear(); + + } finally { + poolLock.unlock(); + } + } + + +} // class ConnPoolByRoute + diff --git a/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java new file mode 100644 index 0000000..faf5e3b --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java @@ -0,0 +1,68 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/PoolEntryRequest.java $ + * $Revision: 652020 $ + * $Date: 2008-04-27 14:23:31 -0700 (Sun, 27 Apr 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.util.concurrent.TimeUnit; + +import org.apache.http.conn.ConnectionPoolTimeoutException; + +/** + * Encapsulates a request for a {@link BasicPoolEntry}. + */ +public interface PoolEntryRequest { + + /** + * Obtains a pool entry with a connection within the given timeout. + * If {@link #abortRequest()} is called before this completes + * an {@link InterruptedException} is thrown. + * + * @param timeout the timeout, 0 or negative for no timeout + * @param tunit the unit for the <code>timeout</code>, + * may be <code>null</code> only if there is no timeout + * + * @return pool entry holding a connection for the route + * + * @throws ConnectionPoolTimeoutException + * if the timeout expired + * @throws InterruptedException + * if the calling thread was interrupted or the request was aborted + */ + BasicPoolEntry getPoolEntry( + long timeout, + TimeUnit tunit) throws InterruptedException, ConnectionPoolTimeoutException; + + /** + * Aborts the active or next call to + * {@link #getPoolEntry(long, TimeUnit)}. + */ + void abortRequest(); + +} diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java new file mode 100644 index 0000000..3af28cc --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RefQueueHandler.java @@ -0,0 +1,48 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueHandler.java $ + * $Revision: 603874 $ + * $Date: 2007-12-13 02:42:41 -0800 (Thu, 13 Dec 2007) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.lang.ref.Reference; + + +/** + * Callback handler for {@link RefQueueWorker RefQueueWorker}. + */ +public interface RefQueueHandler { + + /** + * Invoked when a reference is found on the queue. + * + * @param ref the reference to handle + */ + public void handleReference(Reference<?> ref) + ; +} diff --git a/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java new file mode 100644 index 0000000..9ad5c77 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RefQueueWorker.java @@ -0,0 +1,139 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RefQueueWorker.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + + + +/** + * A worker thread for processing queued references. + * {@link Reference Reference}s can be + * {@link ReferenceQueue queued} + * automatically by the garbage collector. + * If that feature is used, a daemon thread should be executing + * this worker. It will pick up the queued references and pass them + * on to a handler for appropriate processing. + */ +public class RefQueueWorker implements Runnable { + + private final Log log = LogFactory.getLog(getClass()); + + /** The reference queue to monitor. */ + protected final ReferenceQueue<?> refQueue; + + /** The handler for the references found. */ + protected final RefQueueHandler refHandler; + + + /** + * The thread executing this handler. + * This attribute is also used as a shutdown indicator. + */ + protected volatile Thread workerThread; + + + /** + * Instantiates a new worker to listen for lost connections. + * + * @param queue the queue on which to wait for references + * @param handler the handler to pass the references to + */ + public RefQueueWorker(ReferenceQueue<?> queue, RefQueueHandler handler) { + if (queue == null) { + throw new IllegalArgumentException("Queue must not be null."); + } + if (handler == null) { + throw new IllegalArgumentException("Handler must not be null."); + } + + refQueue = queue; + refHandler = handler; + } + + + /** + * The main loop of this worker. + * If initialization succeeds, this method will only return + * after {@link #shutdown shutdown()}. Only one thread can + * execute the main loop at any time. + */ + public void run() { + + if (this.workerThread == null) { + this.workerThread = Thread.currentThread(); + } + + while (this.workerThread == Thread.currentThread()) { + try { + // remove the next reference and process it + Reference<?> ref = refQueue.remove(); + refHandler.handleReference(ref); + } catch (InterruptedException e) { + //@@@ is logging really necessary? this here is the + //@@@ only reason for having a log in this class + if (log.isDebugEnabled()) { + log.debug(this.toString() + " interrupted", e); + } + } + } + } + + + /** + * Shuts down this worker. + * It can be re-started afterwards by another call to {@link #run run()}. + */ + public void shutdown() { + Thread wt = this.workerThread; + if (wt != null) { + this.workerThread = null; // indicate shutdown + wt.interrupt(); + } + } + + + /** + * Obtains a description of this worker. + * + * @return a descriptive string for this worker + */ + @Override + public String toString() { + return "RefQueueWorker::" + this.workerThread; + } + +} // class RefQueueWorker + diff --git a/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java new file mode 100644 index 0000000..5c63933 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java @@ -0,0 +1,301 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/RouteSpecificPool.java $ + * $Revision: 677240 $ + * $Date: 2008-07-16 04:25:47 -0700 (Wed, 16 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.util.ListIterator; +import java.util.Queue; +import java.util.LinkedList; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.util.LangUtils; + + +/** + * A connection sub-pool for a specific route, used by {@link ConnPoolByRoute}. + * The methods in this class are unsynchronized. It is expected that the + * containing pool takes care of synchronization. + */ +public class RouteSpecificPool { + + private final Log log = LogFactory.getLog(getClass()); + + /** The route this pool is for. */ + protected final HttpRoute route; + + /** the maximum number of entries allowed for this pool */ + protected final int maxEntries; + + /** + * The list of free entries. + * This list is managed LIFO, to increase idle times and + * allow for closing connections that are not really needed. + */ + protected final LinkedList<BasicPoolEntry> freeEntries; + + /** The list of threads waiting for this pool. */ + protected final Queue<WaitingThread> waitingThreads; + + /** The number of created entries. */ + protected int numEntries; + + + /** + * Creates a new route-specific pool. + * + * @param route the route for which to pool + * @param maxEntries the maximum number of entries allowed for this pool + */ + public RouteSpecificPool(HttpRoute route, int maxEntries) { + this.route = route; + this.maxEntries = maxEntries; + this.freeEntries = new LinkedList<BasicPoolEntry>(); + this.waitingThreads = new LinkedList<WaitingThread>(); + this.numEntries = 0; + } + + + /** + * Obtains the route for which this pool is specific. + * + * @return the route + */ + public final HttpRoute getRoute() { + return route; + } + + + /** + * Obtains the maximum number of entries allowed for this pool. + * + * @return the max entry number + */ + public final int getMaxEntries() { + return maxEntries; + } + + + /** + * Indicates whether this pool is unused. + * A pool is unused if there is neither an entry nor a waiting thread. + * All entries count, not only the free but also the allocated ones. + * + * @return <code>true</code> if this pool is unused, + * <code>false</code> otherwise + */ + public boolean isUnused() { + return (numEntries < 1) && waitingThreads.isEmpty(); + } + + + /** + * Return remaining capacity of this pool + * + * @return capacity + */ + public int getCapacity() { + return maxEntries - numEntries; + } + + + /** + * Obtains the number of entries. + * This includes not only the free entries, but also those that + * have been created and are currently issued to an application. + * + * @return the number of entries for the route of this pool + */ + public final int getEntryCount() { + return numEntries; + } + + + /** + * Obtains a free entry from this pool, if one is available. + * + * @return an available pool entry, or <code>null</code> if there is none + */ + public BasicPoolEntry allocEntry(final Object state) { + if (!freeEntries.isEmpty()) { + ListIterator<BasicPoolEntry> it = freeEntries.listIterator(freeEntries.size()); + while (it.hasPrevious()) { + BasicPoolEntry entry = it.previous(); + if (LangUtils.equals(state, entry.getState())) { + it.remove(); + return entry; + } + } + } + if (!freeEntries.isEmpty()) { + BasicPoolEntry entry = freeEntries.remove(); + entry.setState(null); + OperatedClientConnection conn = entry.getConnection(); + try { + conn.close(); + } catch (IOException ex) { + log.debug("I/O error closing connection", ex); + } + return entry; + } + return null; + } + + + /** + * Returns an allocated entry to this pool. + * + * @param entry the entry obtained from {@link #allocEntry allocEntry} + * or presented to {@link #createdEntry createdEntry} + */ + public void freeEntry(BasicPoolEntry entry) { + + if (numEntries < 1) { + throw new IllegalStateException + ("No entry created for this pool. " + route); + } + if (numEntries <= freeEntries.size()) { + throw new IllegalStateException + ("No entry allocated from this pool. " + route); + } + freeEntries.add(entry); + } + + + /** + * Indicates creation of an entry for this pool. + * The entry will <i>not</i> be added to the list of free entries, + * it is only recognized as belonging to this pool now. It can then + * be passed to {@link #freeEntry freeEntry}. + * + * @param entry the entry that was created for this pool + */ + public void createdEntry(BasicPoolEntry entry) { + + if (!route.equals(entry.getPlannedRoute())) { + throw new IllegalArgumentException + ("Entry not planned for this pool." + + "\npool: " + route + + "\nplan: " + entry.getPlannedRoute()); + } + + numEntries++; + } + + + /** + * Deletes an entry from this pool. + * Only entries that are currently free in this pool can be deleted. + * Allocated entries can not be deleted. + * + * @param entry the entry to delete from this pool + * + * @return <code>true</code> if the entry was found and deleted, or + * <code>false</code> if the entry was not found + */ + public boolean deleteEntry(BasicPoolEntry entry) { + + final boolean found = freeEntries.remove(entry); + if (found) + numEntries--; + return found; + } + + + /** + * Forgets about an entry from this pool. + * This method is used to indicate that an entry + * {@link #allocEntry allocated} + * from this pool has been lost and will not be returned. + */ + public void dropEntry() { + if (numEntries < 1) { + throw new IllegalStateException + ("There is no entry that could be dropped."); + } + numEntries--; + } + + + /** + * Adds a waiting thread. + * This pool makes no attempt to match waiting threads with pool entries. + * It is the caller's responsibility to check that there is no entry + * before adding a waiting thread. + * + * @param wt the waiting thread + */ + public void queueThread(WaitingThread wt) { + if (wt == null) { + throw new IllegalArgumentException + ("Waiting thread must not be null."); + } + this.waitingThreads.add(wt); + } + + + /** + * Checks whether there is a waiting thread in this pool. + * + * @return <code>true</code> if there is a waiting thread, + * <code>false</code> otherwise + */ + public boolean hasThread() { + return !this.waitingThreads.isEmpty(); + } + + + /** + * Returns the next thread in the queue. + * + * @return a waiting thread, or <code>null</code> if there is none + */ + public WaitingThread nextThread() { + return this.waitingThreads.peek(); + } + + + /** + * Removes a waiting thread, if it is queued. + * + * @param wt the waiting thread + */ + public void removeThread(WaitingThread wt) { + if (wt == null) + return; + + this.waitingThreads.remove(wt); + } + + +} // class RouteSpecificPool diff --git a/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java new file mode 100644 index 0000000..0781e05 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java @@ -0,0 +1,282 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/ThreadSafeClientConnManager.java $ + * $Revision: 673450 $ + * $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +import java.io.IOException; +import java.util.concurrent.TimeUnit; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.http.conn.routing.HttpRoute; +import org.apache.http.conn.scheme.SchemeRegistry; +import org.apache.http.conn.ClientConnectionManager; +import org.apache.http.conn.ClientConnectionOperator; +import org.apache.http.conn.ClientConnectionRequest; +import org.apache.http.conn.ConnectionPoolTimeoutException; +import org.apache.http.conn.ManagedClientConnection; +import org.apache.http.conn.OperatedClientConnection; +import org.apache.http.params.HttpParams; +import org.apache.http.impl.conn.DefaultClientConnectionOperator; + + + +/** + * Manages a pool of {@link OperatedClientConnection client connections}. + * <p> + * This class is derived from <code>MultiThreadedHttpConnectionManager</code> + * in HttpClient 3. See there for original authors. + * </p> + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + * @author <a href="mailto:becke@u.washington.edu">Michael Becke</a> + * + * + * <!-- empty lines to avoid svn diff problems --> + * @version $Revision: 673450 $ $Date: 2008-07-02 10:35:05 -0700 (Wed, 02 Jul 2008) $ + * + * @since 4.0 + */ +public class ThreadSafeClientConnManager implements ClientConnectionManager { + + private final Log log = LogFactory.getLog(getClass()); + + /** The schemes supported by this connection manager. */ + protected SchemeRegistry schemeRegistry; + + /** The pool of connections being managed. */ + protected final AbstractConnPool connectionPool; + + /** The operator for opening and updating connections. */ + protected ClientConnectionOperator connOperator; + + + + /** + * Creates a new thread safe connection manager. + * + * @param params the parameters for this manager + * @param schreg the scheme registry, or + * <code>null</code> for the default registry + */ + public ThreadSafeClientConnManager(HttpParams params, + SchemeRegistry schreg) { + + if (params == null) { + throw new IllegalArgumentException("HTTP parameters may not be null"); + } + this.schemeRegistry = schreg; + this.connOperator = createConnectionOperator(schreg); + this.connectionPool = createConnectionPool(params); + + } // <constructor> + + + @Override + protected void finalize() throws Throwable { + shutdown(); + super.finalize(); + } + + + /** + * Hook for creating the connection pool. + * + * @return the connection pool to use + */ + protected AbstractConnPool createConnectionPool(final HttpParams params) { + + AbstractConnPool acp = new ConnPoolByRoute(connOperator, params); + boolean conngc = true; //@@@ check parameters to decide + if (conngc) { + acp.enableConnectionGC(); + } + return acp; + } + + + /** + * Hook for creating the connection operator. + * It is called by the constructor. + * Derived classes can override this method to change the + * instantiation of the operator. + * The default implementation here instantiates + * {@link DefaultClientConnectionOperator DefaultClientConnectionOperator}. + * + * @param schreg the scheme registry to use, or <code>null</code> + * + * @return the connection operator to use + */ + protected ClientConnectionOperator + createConnectionOperator(SchemeRegistry schreg) { + + return new DefaultClientConnectionOperator(schreg); + } + + + // non-javadoc, see interface ClientConnectionManager + public SchemeRegistry getSchemeRegistry() { + return this.schemeRegistry; + } + + + public ClientConnectionRequest requestConnection( + final HttpRoute route, + final Object state) { + + final PoolEntryRequest poolRequest = connectionPool.requestPoolEntry( + route, state); + + return new ClientConnectionRequest() { + + public void abortRequest() { + poolRequest.abortRequest(); + } + + public ManagedClientConnection getConnection( + long timeout, TimeUnit tunit) throws InterruptedException, + ConnectionPoolTimeoutException { + if (route == null) { + throw new IllegalArgumentException("Route may not be null."); + } + + if (log.isDebugEnabled()) { + log.debug("ThreadSafeClientConnManager.getConnection: " + + route + ", timeout = " + timeout); + } + + BasicPoolEntry entry = poolRequest.getPoolEntry(timeout, tunit); + return new BasicPooledConnAdapter(ThreadSafeClientConnManager.this, entry); + } + + }; + + } + + + // non-javadoc, see interface ClientConnectionManager + public void releaseConnection(ManagedClientConnection conn, long validDuration, TimeUnit timeUnit) { + + if (!(conn instanceof BasicPooledConnAdapter)) { + throw new IllegalArgumentException + ("Connection class mismatch, " + + "connection not obtained from this manager."); + } + BasicPooledConnAdapter hca = (BasicPooledConnAdapter) conn; + if ((hca.getPoolEntry() != null) && (hca.getManager() != this)) { + throw new IllegalArgumentException + ("Connection not obtained from this manager."); + } + + try { + // make sure that the response has been read completely + if (hca.isOpen() && !hca.isMarkedReusable()) { + if (log.isDebugEnabled()) { + log.debug + ("Released connection open but not marked reusable."); + } + // In MTHCM, there would be a call to + // SimpleHttpConnectionManager.finishLastResponse(conn); + // Consuming the response is handled outside in 4.0. + + // make sure this connection will not be re-used + // Shut down rather than close, we might have gotten here + // because of a shutdown trigger. + // Shutdown of the adapter also clears the tracked route. + hca.shutdown(); + } + } catch (IOException iox) { + //@@@ log as warning? let pass? + if (log.isDebugEnabled()) + log.debug("Exception shutting down released connection.", + iox); + } finally { + BasicPoolEntry entry = (BasicPoolEntry) hca.getPoolEntry(); + boolean reusable = hca.isMarkedReusable(); + hca.detach(); + if (entry != null) { + connectionPool.freeEntry(entry, reusable, validDuration, timeUnit); + } + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void shutdown() { + connectionPool.shutdown(); + } + + + /** + * Gets the total number of pooled connections for the given route. + * This is the total number of connections that have been created and + * are still in use by this connection manager for the route. + * This value will not exceed the maximum number of connections per host. + * + * @param route the route in question + * + * @return the total number of pooled connections for that route + */ + public int getConnectionsInPool(HttpRoute route) { + return ((ConnPoolByRoute)connectionPool).getConnectionsInPool( + route); + } + + + /** + * Gets the total number of pooled connections. This is the total number of + * connections that have been created and are still in use by this connection + * manager. This value will not exceed the maximum number of connections + * in total. + * + * @return the total number of pooled connections + */ + public int getConnectionsInPool() { + synchronized (connectionPool) { + return connectionPool.numConnections; //@@@ + } + } + + + // non-javadoc, see interface ClientConnectionManager + public void closeIdleConnections(long idleTimeout, TimeUnit tunit) { + // combine these two in a single call? + connectionPool.closeIdleConnections(idleTimeout, tunit); + connectionPool.deleteClosedConnections(); + } + + public void closeExpiredConnections() { + connectionPool.closeExpiredConnections(); + connectionPool.deleteClosedConnections(); + } + + +} // class ThreadSafeClientConnManager + diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThread.java b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java new file mode 100644 index 0000000..a50e11f --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/WaitingThread.java @@ -0,0 +1,197 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThread.java $ + * $Revision: 649217 $ + * $Date: 2008-04-17 11:32:32 -0700 (Thu, 17 Apr 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + + +import java.util.Date; +import java.util.concurrent.locks.Condition; + + +/** + * Represents a thread waiting for a connection. + * This class implements throwaway objects. It is instantiated whenever + * a thread needs to wait. Instances are not re-used, except if the + * waiting thread experiences a spurious wakeup and continues to wait. + * <br/> + * All methods assume external synchronization on the condition + * passed to the constructor. + * Instances of this class do <i>not</i> synchronize access! + * + * @author <a href="mailto:rolandw at apache.org">Roland Weber</a> + */ +public class WaitingThread { + + /** The condition on which the thread is waiting. */ + private final Condition cond; + + /** The route specific pool on which the thread is waiting. */ + //@@@ replace with generic pool interface + private final RouteSpecificPool pool; + + /** The thread that is waiting for an entry. */ + private Thread waiter; + + /** True if this was interrupted. */ + private boolean aborted; + + + /** + * Creates a new entry for a waiting thread. + * + * @param cond the condition for which to wait + * @param pool the pool on which the thread will be waiting, + * or <code>null</code> + */ + public WaitingThread(Condition cond, RouteSpecificPool pool) { + + if (cond == null) { + throw new IllegalArgumentException("Condition must not be null."); + } + + this.cond = cond; + this.pool = pool; + } + + + /** + * Obtains the condition. + * + * @return the condition on which to wait, never <code>null</code> + */ + public final Condition getCondition() { + // not synchronized + return this.cond; + } + + + /** + * Obtains the pool, if there is one. + * + * @return the pool on which a thread is or was waiting, + * or <code>null</code> + */ + public final RouteSpecificPool getPool() { + // not synchronized + return this.pool; + } + + + /** + * Obtains the thread, if there is one. + * + * @return the thread which is waiting, or <code>null</code> + */ + public final Thread getThread() { + // not synchronized + return this.waiter; + } + + + /** + * Blocks the calling thread. + * This method returns when the thread is notified or interrupted, + * if a timeout occurrs, or if there is a spurious wakeup. + * <br/> + * This method assumes external synchronization. + * + * @param deadline when to time out, or <code>null</code> for no timeout + * + * @return <code>true</code> if the condition was satisfied, + * <code>false</code> in case of a timeout. + * Typically, a call to {@link #wakeup} is used to indicate + * that the condition was satisfied. Since the condition is + * accessible outside, this cannot be guaranteed though. + * + * @throws InterruptedException if the waiting thread was interrupted + * + * @see #wakeup + */ + public boolean await(Date deadline) + throws InterruptedException { + + // This is only a sanity check. We cannot synchronize here, + // the lock would not be released on calling cond.await() below. + if (this.waiter != null) { + throw new IllegalStateException + ("A thread is already waiting on this object." + + "\ncaller: " + Thread.currentThread() + + "\nwaiter: " + this.waiter); + } + + if (aborted) + throw new InterruptedException("Operation interrupted"); + + this.waiter = Thread.currentThread(); + + boolean success = false; + try { + if (deadline != null) { + success = this.cond.awaitUntil(deadline); + } else { + this.cond.await(); + success = true; + } + if (aborted) + throw new InterruptedException("Operation interrupted"); + } finally { + this.waiter = null; + } + return success; + + } // await + + + /** + * Wakes up the waiting thread. + * <br/> + * This method assumes external synchronization. + */ + public void wakeup() { + + // If external synchronization and pooling works properly, + // this cannot happen. Just a sanity check. + if (this.waiter == null) { + throw new IllegalStateException + ("Nobody waiting on this object."); + } + + // One condition might be shared by several WaitingThread instances. + // It probably isn't, but just in case: wake all, not just one. + this.cond.signalAll(); + } + + public void interrupt() { + aborted = true; + this.cond.signalAll(); + } + + +} // class WaitingThread diff --git a/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java new file mode 100644 index 0000000..1844457 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java @@ -0,0 +1,62 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/WaitingThreadAborter.java $ + * $Revision: 649220 $ + * $Date: 2008-04-17 11:40:24 -0700 (Thu, 17 Apr 2008) $ + * + * ==================================================================== + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ + +package org.apache.http.impl.conn.tsccm; + +/** A simple class that can interrupt a {@link WaitingThread}. */ +public class WaitingThreadAborter { + + private WaitingThread waitingThread; + private boolean aborted; + + /** + * If a waiting thread has been set, interrupts it. + */ + public void abort() { + aborted = true; + + if (waitingThread != null) + waitingThread.interrupt(); + + } + + /** + * Sets the waiting thread. If this has already been aborted, + * the waiting thread is immediately interrupted. + * + * @param waitingThread The thread to interrupt when aborting. + */ + public void setWaitingThread(WaitingThread waitingThread) { + this.waitingThread = waitingThread; + if (aborted) + waitingThread.interrupt(); + } + +} diff --git a/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png Binary files differnew file mode 100644 index 0000000..2e2820d --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/doc-files/tsccm-structure.png diff --git a/src/org/apache/http/impl/conn/tsccm/package.html b/src/org/apache/http/impl/conn/tsccm/package.html new file mode 100644 index 0000000..5aca5d4 --- /dev/null +++ b/src/org/apache/http/impl/conn/tsccm/package.html @@ -0,0 +1,205 @@ +<html> +<head> +<!-- +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk/module-client/src/main/java/org/apache/http/impl/conn/tsccm/package.html $ + * $Revision: 653041 $ + * $Date: 2008-05-03 03:39:28 -0700 (Sat, 03 May 2008) $ + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + */ +--> +</head> +<body> + +The implementation of a thread-safe client connection manager. + +<center> +<img src="doc-files/tsccm-structure.png" alt="Relation Diagram"/> +</center> + +<p> +The implementation is structured into three areas, as illustrated +by the diagram above. +Facing the application is the <i>Manager</i> (green), which internally +maintains a <i>Pool</i> (yellow) of connections and waiting threads. +Both Manager and Pool rely on <i>Operations</i> (cyan) to provide the +actual connections. +</p> +<p> +In order to allow connection garbage collection, it is +imperative that hard object references between the areas are +restricted to the relations indicated by arrows in the diagram: +</p> +<ul> +<li>Applications reference only the Manager objects.</li> +<li>Manager objects reference Pool objects, but not vice versa.</li> +<li>Operations objects do not reference either Manager or Pool objects.</li> +</ul> + +<p> +The following table shows a selection of classes and interfaces, +and their assignment to the three areas. +</p> +<center> +<table border="1"> +<colgroup> + <col width="50%"/> + <col width="50%"/> +</colgroup> + +<tr> +<td style="text-align: center; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager} +</td> +<td style="text-align: center; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.AbstractConnPool} +</td> +</tr> + +<tr> +<td style="text-align: center; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPooledConnAdapter} +</td> +<td style="text-align: center; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute} +</td> +</tr> + +<!-- appears on both sides! --> + +<tr> +<td style="text-align: right; background-color: #00ff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +</td> +<td style="text-align: left; background-color: #ffff00;"> +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +</td> +</tr> + +<!-- ====================== --> + +<tr style="border-width: 5px;"> +</tr> + +<tr> +<td colspan="2" style="text-align: center; background-color: #00ffff;"> +{@link org.apache.http.conn.ClientConnectionOperator} +</td> +</tr> + +<tr> +<td colspan="2" style="text-align: center; background-color: #00ffff;"> +{@link org.apache.http.conn.OperatedClientConnection} +</td> +</tr> + +</table> +</center> + +<p> +The Manager area has implementations for the connection management +interfaces {@link org.apache.http.conn.ClientConnectionManager} +and {@link org.apache.http.conn.ManagedClientConnection}. +The latter is an adapter from managed to operated connections, based on a +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}. +<br/> +The Pool area shows an abstract pool class +{@link org.apache.http.impl.conn.tsccm.AbstractConnPool} +and a concrete implementation +{@link org.apache.http.impl.conn.tsccm.ConnPoolByRoute} +which uses the same basic algorithm as the +<code>MultiThreadedHttpConnectionManager</code> +in HttpClient 3.x. +A pool contains instances of +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry}. +Most other classes in this package also belong to the Pool area. +<br/> +In the Operations area, you will find only the interfaces for +operated connections as defined in the org.apache.http.conn package. +The connection manager will work with all correct implementations +of these interfaces. This package therefore does not define anything +specific to the Operations area. +</p> + +<p> +As you have surely noticed, the +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntry} +appears in both the Manager and Pool areas. +This is where things get tricky for connection garbage collection. +<br/> +A connection pool may start a background thread to implement cleanup. +In that case, the connection pool will not be garbage collected until +it is shut down, since the background thread keeps a hard reference +to the pool. The pool itself keeps hard references to the pooled entries, +which in turn reference idle connections. Neither of these is subject +to garbage collection. +Only the shutdown of the pool will stop the background thread, +thereby enabling garbage collection of the pool objects. +<br/> +A pool entry that is passed to an application by means of a connection +adapter will move from the Pool area to the Manager area. When the +connection is released by the application, the manager returns the +entry back to the pool. With that step, the pool entry moves from +the Manager area back to the Pool area. +While the entry is in the Manager area, the pool MUST NOT keep a +hard reference to it. +</p> + +<p> +The purpose of connection garbage collection is to detect when an +application fails to return a connection. In order to achieve this, +the only hard reference to the pool entry in the Manager area is +in the connection wrapper. The manager will not keep a hard reference +to the connection wrapper either, since that wrapper is effectively +moving to the Application area. +If the application drops it's reference to the connection wrapper, +that wrapper will be garbage collected, and with it the pool entry. +<br/> +In order to detect garbage collection of pool entries handed out +to the application, the pool keeps a <i>weak reference</i> to the +entry. Instances of +{@link org.apache.http.impl.conn.tsccm.BasicPoolEntryRef} +combine the weak reference with information about the route for +which the pool entry was allocated. If one of these entry references +becomes stale, the pool can accommodate for the lost connection. +This is triggered either by a background thread waiting for the +references to be queued by the garbage collector, or by the +application calling a {@link + org.apache.http.conn.ClientConnectionManager#closeIdleConnections cleanup} +method of the connection manager. +<br/> +Basically the same trick is used for detecting garbage collection +of the connection manager itself. The pool keeps a weak reference +to the connection manager that created it. However, this will work +only if there is a background thread to detect when that reference +is queued by the garbage collector. Otherwise, a finalizer of the +connection manager will shut down the pool and release it's resources. +</p> + + +</body> +</html> |