Index: java/org/apache/commons/httpclient/HostConfiguration.java =================================================================== RCS file: java/org/apache/commons/httpclient/HostConfiguration.java diff -N java/org/apache/commons/httpclient/HostConfiguration.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/HostConfiguration.java 20 Nov 2002 14:42:58 -0000 @@ -0,0 +1,176 @@ +package org.apache.commons.httpclient; + +import java.security.InvalidParameterException; + +/** + */ +public class HostConfiguration implements Cloneable{ + + private String host; + private int port; + private String protocol; + private boolean hostSet; + + private String proxyHost; + private int proxyPort; + private boolean proxySet; + + /** + * Constructor for HostConfiguration. + */ + public HostConfiguration() { + + this.host = null; + this.port = -1; + this.protocol = null; + this.hostSet = false; + + this.proxyHost = null; + this.proxyPort = -1; + this.proxySet = false; + + } + + /** + * Copy constructor for HostConfiguration + * + * @param hostConfiguration the hostConfiguration to copy + */ + public HostConfiguration( HostConfiguration hostConfiguration ) { + + // wrap all of the assignments in a synchronized block to avoid + // having to negotiate the monitor for each method call + synchronized( hostConfiguration ) { + this.host = hostConfiguration.getHost(); + this.port = hostConfiguration.getPort(); + this.protocol = hostConfiguration.getProtocol(); + this.hostSet = hostConfiguration.isHostSet(); + + this.proxyHost = hostConfiguration.getProxyHost(); + this.proxyPort = hostConfiguration.getProxyPort(); + this.proxySet = hostConfiguration.isProxySet(); + } + + } + + public synchronized boolean isHostSet() { + return hostSet; + } + + public synchronized void setHost( + String host, + int port, + String protocol + ) { + if ( host == null ) { + throw new InvalidParameterException( "host must not be null" ); + } + if ( protocol == null ) { + throw new InvalidParameterException( "protocol must not be null" ); + } + + this.host = host; + this.port = port; + this.protocol = protocol; + + this.hostSet = true; + + } + + public synchronized String getHostURL() { + + if ( !hostSet ) { + throw new IllegalStateException( + "a default host must be set to create a host URL" + ); + } + + String url = protocol + "://" + host; + + if ( port != -1 ) { + url += ":" + port; + } + + return url; + + } + + /** + * Returns the host. + * @return String + */ + public synchronized String getHost() { + return host; + } + + /** + * Returns the port. + * @return int + */ + public synchronized int getPort() { + return port; + } + + /** + * Returns the protocol. + * @return String + */ + public synchronized String getProtocol() { + return protocol; + } + + public synchronized boolean isProxySet() { + return proxySet; + } + + public synchronized void setProxy( String proxyHost, int proxyPort ) { + + this.proxyHost = proxyHost; + this.proxyPort = proxyPort; + + this.proxySet = true; + + } + + /** + * Returns the proxyHost. + * @return String + */ + public synchronized String getProxyHost() { + return proxyHost; + } + + /** + * Returns the proxyPort. + * @return int + */ + public synchronized int getProxyPort() { + return proxyPort; + } + + /** + * @see java.lang.Object#equals(java.lang.Object) + */ + public boolean equals(Object o) { + + if ( o instanceof HostConfiguration ) { + + HostConfiguration config = (HostConfiguration)o; + + return ( + proxyPort == config.getProxyPort() + && ( + proxyHost == null + ? config.getProxyHost() == null + : proxyHost.equals( config.getProxyHost() ) + ) + && getHostURL().equals( config.getHostURL() ) + ); + + } else { + return false; + } + + } + +} Index: java/org/apache/commons/httpclient/HttpClient.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpClient.java,v retrieving revision 1.60 diff -u -r1.60 HttpClient.java --- java/org/apache/commons/httpclient/HttpClient.java 12 Nov 2002 09:58:22 -0000 1.60 +++ java/org/apache/commons/httpclient/HttpClient.java 20 Nov 2002 14:43:09 -0000 @@ -67,6 +67,8 @@ import java.io.IOException; import java.net.URL; +import java.security.InvalidParameterException; + import javax.net.ssl.SSLSocketFactory; @@ -80,7 +82,9 @@ * @author Rodney Waldhoff * @author Sean C. Sullivan * @author dIon Gillard - * @author Ortwin Glück + * @author Ortwin Gl?ck + * @author Michael Becke + * * @version $Revision: 1.60 $ $Date: 2002/11/12 09:58:22 $ */ @@ -98,14 +102,26 @@ * Constructor. */ public HttpClient() { + this( new SimpleHttpConnectionManager() ); } - // ----------------------------------------------------- Instance Variables - /** - * My {@link HttpConnection connection}. + * Constructor. */ - private HttpConnection connection = null; + public HttpClient( HttpConnectionManager httpConnectionManager ) { + + if ( httpConnectionManager == null ) { + throw new InvalidParameterException("httpConnectionManager cannot be null"); + } + + this.state = new HttpState(); + this.state.setHttpConnectionManager( httpConnectionManager ); + + this.hostConfiguration = new HostConfiguration(); + + } + + // ----------------------------------------------------- Instance Variables /** * My {@link HttpState state}. @@ -114,10 +130,17 @@ private SSLSocketFactory sslSocketFactory = null; + /** the timout when waiting for a connection from the connectionManager */ + private long httpConnectionTimeout = 0; + private int timeoutInMilliseconds = 0; private int connectionTimeout = 0; + private HostConfiguration hostConfiguration; + + private boolean strictMode = false; + // ------------------------------------------------------------- Properties /** @@ -126,10 +149,7 @@ * @see #setState(HttpState) * @return the shared client state */ - public HttpState getState() { - if (null == state) { - state = new HttpState(); - } + public synchronized HttpState getState() { return state; } @@ -139,16 +159,38 @@ * @see #getState() * @param state the new state for the client */ - public void setState(HttpState state) { + public synchronized void setState(HttpState state) { this.state = state; } /** + * + * @param strictMode true if strict mode should be used + * + * @see #isStrictMode() + * + */ + public synchronized void setStrictMode(boolean strictMode) { + this.strictMode = strictMode; + } + + /** + * + * @return true if strict mode being used + * + * @see #setStrictMode(boolean) + * + */ + public synchronized boolean isStrictMode() { + return strictMode; + } + + /** * Specifies an alternative factory for SSL sockets. * @see HttpConnection#setSSLSocketFactory(SSLSocketFactory) HttpConnection.setSSLSocketFactory * @param sslSocketFactory a living instance of the alternative SSLSocketFactory */ - public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { + public synchronized void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { this.sslSocketFactory = sslSocketFactory; } @@ -160,34 +202,44 @@ * @param newTimeoutInMilliseconds Timeout in milliseconds * */ - public void setTimeout(int newTimeoutInMilliseconds) { + public synchronized void setTimeout(int newTimeoutInMilliseconds) { this.timeoutInMilliseconds = newTimeoutInMilliseconds; } /** + * Sets the timeout used when retrieving an HttpConnection from the + * HttpConnectionManager. + * + * @param timeout the timeout in milliseconds + * + * @see HttpConnectionManager#getConnection(String, long) + */ + public synchronized void setHttpConnectionFactoryTimeout( long timeout ) { + this.httpConnectionTimeout = timeout; + } + + /** * Sets the timeout until a connection is etablished. A value of 0 means * the timeout is not used. The default value is 0. * @see HttpConnection#setConnectionTimeout(int) * @param newTimeoutInMilliseconds Timeout in milliseconds. */ - public void setConnectionTimeout(int newTimeoutInMilliseconds) { + public synchronized void setConnectionTimeout(int newTimeoutInMilliseconds) { this.connectionTimeout = newTimeoutInMilliseconds; } // --------------------------------------------------------- Public Methods /** - * Start an HTTP session with the server at the given - * host and port. + * @deprecated use hostConfiguration + * + * Sets the host, port and protocol(http) to be used when executing a + * method. + * * @param host the host to connect to * @param port the port to connect to * - * @see #startSession(String, int, boolean) - * @see #startSession(String, int, Credentials) - * @see #startSession(String, int, Credentials, boolean) - * @see #startSession(String, int, String, int) - * @see #endSession() - * + * @see #getHostConfiguration() */ public void startSession(String host, int port) { log.trace("enter HttpClient.startSession(String, int)"); @@ -195,19 +247,16 @@ } /** - * Start an HTTP or HTTPS session with the server at the given - * host and port. + * @deprecated use hostConfiguration + * + * Sets the host, port and protocol to be used when executing a method. + * * @param host the host to connect to * @param port the port to connect to * @param https when true, create an HTTPS session * - * @see #startSession(String, int) - * @see #startSession(String, int, Credentials) - * @see #startSession(String, int, Credentials, boolean) - * @see #startSession(String, int, String, int) - * @see #endSession() - * - */ + * @see #getHostConfiguration() + */ public void startSession(String host, int port, boolean https) { log.trace("enter HttpClient.startSession(String, int, boolean)"); @@ -215,48 +264,47 @@ log.debug("HttpClient.startSession(String,int,boolean): Host:" + host + " Port:" + port + " HTTPS:" + https); } - connection = new HttpConnection(host, port, https); + + this.hostConfiguration.setHost( + host, + port, + https ? "https" : "http" + ); } /** - * Start an HTTP session with the server at the given - * host and port using the given default - * default credentials. + * @deprecated use hostConfiguration and httpState + * + * Sets the host, port, protocol(http) and credentials to be used when + * executing a method. * * @param host the host to connect to * @param port the port to connect to * @param creds the default credentials to use * - * @see #startSession(String, int, boolean) - * @see #startSession(String, int) + * @see #getHostConfiguration() + * @see #getState() * @see #startSession(String, int, Credentials, boolean) - * @see #startSession(String, int, String, int) - * @see #endSession() - * */ public void startSession(String host, int port, Credentials creds) { log.trace("enter HttpClient.startSession(String, int, Credentials)"); startSession(host, port, creds, false); } - /** - * Start an HTTP or HTTPS session with the server at the given - * host and port using the given default - * default credentials. + * @deprecated use hostConfiguration and httpState + * + * Sets the host, port, protocol and credentials to be used when executing a + * method. * * @param host the host to connect to * @param port the port to connect to * @param creds the default credentials to use * @param https when true, create an HTTPS session * - * @see #startSession(String, int, boolean) - * @see #startSession(String, int) - * @see #startSession(String, int, Credentials) - * @see #startSession(String, int, String, int) - * @see #endSession() - * - */ + * @see #getHostConfiguration() + * @see #getState() + */ public void startSession(String host, int port, Credentials creds, boolean https) { log.trace("enter HttpClient.startSession(String, int, Credentials, boolean)"); @@ -268,22 +316,30 @@ + " HTTPS:" + https); } getState().setCredentials(null, creds); - connection = new HttpConnection(host, port, https); + this.hostConfiguration.setHost( + host, + port, + https ? "https" : "http" + ); } - /** - * Start an HTTP or HTTPS session with the server specified by the scheme, - * userinfo, host and port of the given uri. + * @deprecated use hostConfiguration and httpState + * + * Sets the host, port, protocol and credentials to be used when executing a + * method using the server specified by the scheme, userinfo, host and port + * of the given uri. *

* Note that the path component is not utilized. *

* @param uri an HttpURL or HttpsURL instance; the * {@link URI URI} from which the scheme, userinfo, host and port of the * session are determined + * * @exception IllegalStateException not enough information to process - * @see #startSession - * @see #endSession + * + * @see #getHostConfiguration() + * @see #getState() */ public void startSession(URI uri) throws URIException { log.trace("enter HttpClient.startSession(URI)"); @@ -315,29 +371,27 @@ throw new IllegalStateException ("HttpURL or HttpsURL instance required"); } - connection = new HttpConnection(host, port, secure); + this.hostConfiguration.setHost( + host, + port, + secure ? "https" : "http" + ); } - /** - * Start an HTTP or HTTPS session with the server specified - * by the protocol, host and port of the given - * url. + * @deprecated use hostConfiguration + * + * Sets the host, port and protocol to be used when executing a method. *

* Note that everything but the protocol, host and port of the * given url is ignored. *

- * @param url the {@link URL URL} from which the protocol, host, - * and port of the session are determined - * - * @see #startSession(String, int, boolean) - * @see #startSession(String, int) - * @see #startSession(String, int, Credentials) - * @see #startSession(String, int, Credentials, boolean) - * @see #startSession(java.net.URL, Credentials) - * @see #startSession(String, int, String, int) - * @see #endSession() + * @param url the {@link URL URL} from which the protocol, host, and port of + * the session are determined + * + * @exception IllegalArgumentException if the protocol is not http or https * + * @see #getHostConfiguration() */ public void startSession(URL url) { log.trace("enter HttpClient.startSession(String, int, Credentials, boolean)"); @@ -355,25 +409,22 @@ } /** - * Start an HTTP or HTTPS session with the server specified - * by the protocol, host and port of the given - * url, using the given credentials by default. + * @deprecated use hostConfiguration and httpState + * + * Sets the host, port, protocol and credentials to be used when executing a + * method. *

* Note that everything but the protocol, host and port of the * given url is ignored. *

+ * @param url the {@link URL URL} from which the protocol, host, and port of + * the session are determined * @param creds the default credentials to use - * @param url the {@link URL URL} from which the protocol, host, - * and port of the session are determined - * - * @see #startSession(java.net.URL) - * @see #startSession(String, int, boolean) - * @see #startSession(String, int) - * @see #startSession(String, int, Credentials) - * @see #startSession(String, int, Credentials, boolean) - * @see #startSession(String, int, String, int) - * @see #endSession() + * + * @exception IllegalArgumentException if the protocol is not http or https * + * @see #getHostConfiguration() + * @see #getState() */ public void startSession(URL url, Credentials creds) { log.trace("enter HttpClient.startSession(URL, Credentials)"); @@ -382,100 +433,150 @@ } /** - * Start an HTTP session with the server specified - * by the given host and port - * via the given proxyhost and proxyport. + * @deprecated use hostConfiguration + * + * Sets the host, port, protocol(http) and proxy to be used when executing a + * method. + * * @param host the host to connect to * @param port the port to connect to * @param proxyhost the proxy host to connect via * @param proxyport the proxy port to connect via * - * @see #endSession() - * + * @see #getHostConfiguration() */ public void startSession(String host, int port, String proxyhost, int proxyport) { log.trace("enter HttpClient.startSession(String, int, String, int)"); startSession(host, port, proxyhost, proxyport, false); } - /** - * Start an HTTP session with the server specified - * by the given host and port - * via the given proxyhost and proxyport. - * + * @deprecated use hostConfiguration + * + * Sets the host, port, protocol and proxy to be used when executing a + * method. + * * @param host the host to connect to * @param port the port to connect to * @param proxyhost the proxy host to connect via * @param proxyport the proxy port to connect via * @param secure whether or not to connect using HTTPS - * - * @see #endSession() - * + * + * @see #getHostConfiguration() */ public void startSession(String host, int port, String proxyhost, int proxyport, boolean secure) { log.trace("enter HttpClient.startSession(String, int, String, int, boolean)"); - connection = new HttpConnection(proxyhost, proxyport, host, port, secure); + this.hostConfiguration.setHost( + host, + port, + secure ? "https" : "http" + ); + this.hostConfiguration.setProxy( proxyhost, proxyport ); } /** - * Execute the given {@link HttpMethod} using my current - * {@link HttpConnection connection} and {@link HttpState}. + * Executes the given method. * - * @param method the {@link HttpMethod} to execute. This parameter must be non-null. + * @param method the {@link HttpMethod} to execute. * @return the method's response code * * @throws java.io.IOException if an I/O error occurs * @throws HttpException if a protocol exception occurs - * @throws IllegalStateException if the session has not been started - * */ - public synchronized int executeMethod(HttpMethod method) - throws IOException, HttpException, IllegalStateException { + public int executeMethod(HttpMethod method) + throws IOException, HttpException { + log.trace("enter HttpClient.executeMethod(HttpMethod)"); + return executeMethod(getHostConfiguration(), method); + + } + + /** + * Executes the given method. + * + * @param method the {@link HttpMethod} to execute. + * @return the method's response code + * + * @throws java.io.IOException if an I/O error occurs + * @throws HttpException if a protocol exception occurs + */ + public int executeMethod(HostConfiguration hostConfiguration, HttpMethod method) + throws IOException, HttpException { + log.trace("enter HttpClient.executeMethod(HostConfiguration,HttpMethod)"); if (null == method) { throw new NullPointerException("HttpMethod parameter"); } - if (null == connection) { - throw new IllegalStateException( - "The startSession method must be called before executeMethod"); + int soTimeout = 0; + boolean strictMode = false; + SSLSocketFactory socketFactory = null; + int connectionTimeout = 0; + long httpConnectionTimeout = 0; + HttpState state = null; + HostConfiguration defaultHostConfiguration = null; + + /* access all synchronized data in a single block, this will keeps us + * from accessing data asynchronously as well having to regain the lock + * for each item. + */ + synchronized ( this ) { + soTimeout = this.timeoutInMilliseconds; + strictMode = this.strictMode; + socketFactory = this.sslSocketFactory; + connectionTimeout = this.connectionTimeout; + httpConnectionTimeout = this.httpConnectionTimeout; + state = getState(); + defaultHostConfiguration = getHostConfiguration(); + } + + HostConfiguration methodConfiguration = new HostConfiguration(hostConfiguration); + + if ( hostConfiguration != defaultHostConfiguration ) { + // we may need to appy some defaults + if ( !methodConfiguration.isHostSet() ) { + methodConfiguration.setHost( + defaultHostConfiguration.getHost(), + defaultHostConfiguration.getPort(), + defaultHostConfiguration.getProtocol() + ); + } + if ( !methodConfiguration.isProxySet() && defaultHostConfiguration.isProxySet() ) { + methodConfiguration.setProxy( + defaultHostConfiguration.getProxyHost(), + defaultHostConfiguration.getProxyPort() + ); + } } + // FIXME: need to pass proxy config somehow + HttpConnection connection = state.getHttpConnectionManager().getConnection( + methodConfiguration, + httpConnectionTimeout + ); + + method.setStrictMode(strictMode); + if (!connection.isOpen()) { - connection.setSSLSocketFactory(sslSocketFactory); - connection.setSoTimeout(timeoutInMilliseconds); + connection.setSSLSocketFactory(socketFactory); + connection.setSoTimeout(soTimeout); connection.setConnectionTimeout(connectionTimeout); connection.open(); if (connection.isProxied() && connection.isSecure()) { method = new ConnectMethod(method); } } - return method.execute(getState(), connection); + + return method.execute(state, connection); } /** - * End the current session, closing my the associated - * {@link HttpConnection connection} if any. - * - * @see #startSession(String, int) - * @see #startSession(String, int, boolean) - * @see #startSession(String, int, Credentials) - * @see #startSession(String, int, Credentials, boolean) - * @see #startSession(java.net.URL) - * @see #startSession(java.net.URL, Credentials) - * @see #startSession(String, int, String, int) - * - * @throws java.io.IOException when i/o errors occur closing the connection - * + * @deprecated this method has no effect. HttpMethod.releaseConnection() + * should be used to release resources after a HttpMethod has been executed. + * + * @see HttpMethod#releaseConnection() */ public void endSession() throws IOException { - log.trace("enter HttpClient.endSession()"); - if (null != connection) { - connection.close(); - connection = null; - } } /** @@ -485,7 +586,7 @@ * the session has not been started via startSession. */ public String getHost() { - return connection != null ? connection.getHost() : null; + return hostConfiguration.getHost(); } /** @@ -495,6 +596,23 @@ * has not been started via startSession(). */ public int getPort() { - return connection != null ? connection.getPort() : -1; + return hostConfiguration.getPort(); } + + /** + * Returns the hostConfiguration. + * @return HostConfiguration + */ + public synchronized HostConfiguration getHostConfiguration() { + return hostConfiguration; + } + + /** + * Sets the hostConfiguration. + * @param hostConfiguration The hostConfiguration to set + */ + public synchronized void setHostConfiguration(HostConfiguration hostConfiguration) { + this.hostConfiguration = hostConfiguration; + } + } Index: java/org/apache/commons/httpclient/HttpConnection.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnection.java,v retrieving revision 1.24 diff -u -r1.24 HttpConnection.java --- java/org/apache/commons/httpclient/HttpConnection.java 12 Nov 2002 09:58:22 -0000 1.24 +++ java/org/apache/commons/httpclient/HttpConnection.java 20 Nov 2002 14:43:00 -0000 @@ -83,7 +83,7 @@ *

* @author Rod Waldhoff * @author Sean C. Sullivan - * @author Ortwin Glück + * @author Ortwin Gl?ck * @version $Revision: 1.24 $ $Date: 2002/11/12 09:58:22 $ */ public class HttpConnection { @@ -744,6 +744,29 @@ closeSocketAndStreams(); } + /** + * Returns the httpConnectionManager. + * @return HttpConnectionManager + */ + public HttpConnectionManager getHttpConnectionManager() { + return httpConnectionManager; + } + + /** + * Sets the httpConnectionManager. + * @param httpConnectionManager The httpConnectionManager to set + */ + public void setHttpConnectionManager(HttpConnectionManager httpConnectionManager) { + this.httpConnectionManager = httpConnectionManager; + } + + public void releaseConnection() { + log.trace("enter HttpConnection.releaseConnection()"); + if ( httpConnectionManager != null ) { + httpConnectionManager.releaseConnection( this ); + } + } + // ------------------------------------------------------ Protected Methods @@ -847,6 +870,7 @@ return sslSocketFactory.createSocket(host, port); } } + // ------------------------------------------------------------- Attributes /** Log object for this class. */ @@ -883,4 +907,7 @@ private boolean _tunnelEstablished = false; /** Timeout until connection established (Socket created). 0 means no timeout. */ private int connect_timeout = 0; + /** the connection manager that created this connection or null */ + private HttpConnectionManager httpConnectionManager; + } Index: java/org/apache/commons/httpclient/HttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnectionManager.java,v retrieving revision 1.11 diff -u -r1.11 HttpConnectionManager.java --- java/org/apache/commons/httpclient/HttpConnectionManager.java 29 Sep 2002 21:59:47 -0000 1.11 +++ java/org/apache/commons/httpclient/HttpConnectionManager.java 20 Nov 2002 14:43:02 -0000 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnectionManager.java,v 1.11 2002/09/29 21:59:47 sullis Exp $ - * $Revision: 1.11 $ - * $Date: 2002/09/29 21:59:47 $ + * $Header: $ + * $Revision: $ + * $Date: $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -28,7 +28,7 @@ * Alternately, this acknowlegement may appear in the software itself, * if and wherever such third-party acknowlegements normally appear. * - * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software * Foundation" must not be used to endorse or promote products derived * from this software without prior written permission. For written * permission, please contact apache@apache.org. @@ -62,366 +62,57 @@ package org.apache.commons.httpclient; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - import java.net.MalformedURLException; -import java.net.URL; -import java.util.HashMap; -import java.util.Map; -import java.util.LinkedList; /** - * Manages a set of HttpConnections for various host:ports. This class is - * used by HttpMultiClient. - * - * @author Marc A. Saegesser */ -public class HttpConnectionManager -{ - // -------------------------------------------------------- Class Variables - /** Log object for this class. */ - private static final Log log = LogFactory.getLog(HttpConnectionManager.class); - - // ----------------------------------------------------- Instance Variables - private Map mapHosts = new HashMap(); - private Map mapNumConnections = new HashMap(); - private int maxConnections = 2; // Per RFC 2616 sec 8.1.4 - private String proxyHost = null; - private int proxyPort = -1; - - /** - * No-args constructor - */ - public HttpConnectionManager() { - } - - /** - * Set the proxy host to use for all connections. - * - * @param proxyHost - the proxy host name - */ - public void setProxyHost(String proxyHost) { - this.proxyHost = proxyHost; - } - - /** - * Get the proxy host. - * - * @return the proxy host name - */ - public String getProxyHost() { - return proxyHost; - } - - /** - * Set the proxy port to use for all connections. - * - * @param proxyPort - the proxy port number - */ - public void setProxyPort(int proxyPort) { - this.proxyPort = proxyPort; - } - - /** - * Get the proxy port number. - * - * @return the proxy port number - */ - public int getProxyPort() { - return proxyPort; - } - - /** - * Set the maximum number of connections allowed for a given host:port. - * Per RFC 2616 section 8.1.4, this value defaults to 2. - * - * @param maxConnections - number of connections allowed for each host:port - */ - public void setMaxConnectionsPerHost(int maxConnections) { - this.maxConnections = maxConnections; - } - - /** - * Get the maximum number of connections allowed for a given host:port. - * - * @return The maximum number of connections allowed for a given host:port. - */ - public int getMaxConnectionsPerHost() { - return maxConnections; - } - - /** - * Get an HttpConnection for a given URL. The URL must be fully - * specified (i.e. contain a protocol and a host (and optional port number). - * If the maximum number of connections for the host has been reached, this - * method will block forever until a connection becomes available. - * - * @param sURL - a fully specified URL. - * @return an HttpConnection for the given host:port - * @exception java.net.MalformedURLException - * @exception org.apache.commons.httpclient.HttpException - - * If no connection becomes available before the timeout expires - */ - public HttpConnection getConnection(String sURL) - throws HttpException, MalformedURLException { - return getConnection(sURL, 0); - } +public interface HttpConnectionManager { /** - * Return the port provided if not -1 (default), return 443 if the - * protocol is HTTPS, otherwise 80. + * Gets an HttpConnection for a given host configuration. If a connection is + * not available this method will block until one is. * - * This functionality is a URLUtil and may be better off in URIUtils + * The connection manager should be registered with any HttpConnection that + * is created. * - * @param protocol the protocol to use to get the port for, e.g. http or - * https - * @param port the port provided with the url (could be -1) - * @return the port for the specified port and protocol. + * @param hostConfiguration the host configuration to use to configure the + * connection + * + * @return an HttpConnection for the given configuration + * + * @see HttpConnection#setHttpConnectionManager(HttpConnectionManager) */ - private static int getPort(String protocol, int port) { - log.trace("HttpConnectionManager.getPort(String, port)"); + public HttpConnection getConnection(HostConfiguration hostConfiguration) + throws MalformedURLException; - // default to provided port - int portForProtocol = port; - if (portForProtocol == -1) { - if (protocol.equalsIgnoreCase("HTTPS")) { - portForProtocol = 443; - } else { - portForProtocol = 80; - } - } - return portForProtocol; - } - /** - * Get an HttpConnection for a given URL. The URL must be fully - * specified (i.e. contain a protocol and a host (and optional port number). - * If the maximum number of connections for the host has been reached, this - * method will block for timeout milliseconds or until a - * connection becomes available. If no connection becomes available before - * the timeout expires an HttpException exception will be thrown. + * Gets an HttpConnection for a given host configuration. If a connection is + * not available, this method will block for at most the specified number of + * milliseconds or until a connection becomes available. * - * @param sURL - a fully specified URL. - * @param timeout - the time (in milliseconds) to wait for a connection - * to become available - * @return an HttpConnection for the given host:port - * @exception java.net.MalformedURLException - * @exception org.apache.commons.httpclient.HttpException - - * If no connection becomes available before the timeout expires - */ - public HttpConnection getConnection(String sURL, long timeout) - throws HttpException, MalformedURLException { - log.trace("enter HttpConnectionManager.getConnection(String, long)"); - - // FIXME: This method is too big - if (sURL == null) { - throw new MalformedURLException("URL is null"); - } - - URL url = new URL(sURL); - // Get the protocol and port (use default port if not specified) - String protocol = url.getProtocol(); - String host = url.getHost(); - int port = HttpConnectionManager.getPort(protocol, url.getPort()); - final String hostAndPort = host + ":" + port; - - if (log.isDebugEnabled()) { - log.debug("HttpConnectionManager.getConnection: key = " - + hostAndPort); - } - - // Look for a list of connections for the given host:port - LinkedList listConnections = getConnections(hostAndPort); - - HttpConnection conn = null; - // get a connection from the 'pool', waiting for 'timeout' if no - // connections are currently available - synchronized(listConnections) { - if (listConnections.size() > 0) { - conn = (HttpConnection)listConnections.removeFirst(); - } else { - // get number of connections to host:port - Integer numConnections = getConnectionsInUse(hostAndPort); - if (numConnections.intValue() < maxConnections) { - // Create a new connection - boolean isSecure = protocol.equalsIgnoreCase("HTTPS"); - conn = new HttpConnection(proxyHost, proxyPort, host, port, - isSecure); - numConnections = new Integer(numConnections.intValue() + 1); - mapNumConnections.put(hostAndPort, numConnections); - } else { - conn = waitForConnection(listConnections, timeout); - } - } - } - - return conn; - } - - /** - * Get the pool (list) of connections available for the given host and port + * The connection manager should be registered with any HttpConnection that + * is created. * - * @param hostAndPort the key for the connection pool - * @return a pool (list) of connections available for the given key + * @param hostConfiguration the host configuration to use to configure the + * connection + * @param timeout - the time (in milliseconds) to wait for a connection to + * become available, 0 to specify an infinite timeout + * + * @return an HttpConnection for the given configuraiton + * + * @exception org.apache.commons.httpclient.HttpException if no connection + * becomes available before the timeout expires + * + * @see HttpConnection#setHttpConnectionManager(HttpConnectionManager) */ - private LinkedList getConnections(String hostAndPort) { - log.trace("enter HttpConnectionManager.getConnections(String)"); + public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) + throws HttpException, MalformedURLException; - // Look for a list of connections for the given host:port - LinkedList listConnections = null; - synchronized (mapHosts) { - listConnections = (LinkedList) mapHosts.get(hostAndPort); - if (listConnections == null) { - // First time for this host:port - listConnections = new LinkedList(); - mapHosts.put(hostAndPort, listConnections); - mapNumConnections.put(hostAndPort, new Integer(0)); - } - } - return listConnections; - } - /** - * Get the number of connections in use for the key + * Releases the given HttpConnection for use by other requests. * - * @param hostAndPort the key that connections are tracked on - * @return the number of connections in use for the given key - */ - public Integer getConnectionsInUse(String hostAndPort) { - log.trace("enter HttpConnectionManager.getConnectionsInUse(String)"); - // FIXME: Shouldn't this be synchronized on mapNumConnections? or - // mapHosts? - - Integer numConnections = (Integer)mapNumConnections.get(hostAndPort); - if (numConnections == null) { - log.error("HttpConnectionManager.getConnection: " - + "No connection count for " + hostAndPort); - // This should never happen, but just in case we'll try to recover. - numConnections = new Integer(0); - mapNumConnections.put(hostAndPort, numConnections); - } - return numConnections; - } - - /** - * wait for a connection from the pool - */ - private HttpConnection waitForConnection(LinkedList pool, long timeout) - throws HttpException { - log.trace("enter HttpConnectionManager.waitForConnection(LinkedList, long)"); - - // No connections available, so wait - // Start the timeout thread - TimeoutThread threadTimeout = new TimeoutThread(); - threadTimeout.setTimeout(timeout); - threadTimeout.setWakeupThread(Thread.currentThread()); - threadTimeout.start(); - - HttpConnection conn = null; - // wait for the connection to be available - while(conn == null){ // spin lock - try { - log.debug("HttpConnectionManager.getConnection: waiting for " - + "connection from " + pool); - pool.wait(); - } catch (InterruptedException e) { - throw new HttpException("Timeout waiting for connection."); - } - if (pool.size() > 0) { - conn = (HttpConnection)pool.removeFirst(); - threadTimeout.interrupt(); - } - } - return conn; - } - - /** - * Make the given HttpConnection available for use by other requests. - * If another thread is blocked in getConnection() waiting for a connection - * for this host:port, they will be woken up. - * * @param conn - The HttpConnection to make available. */ - public void releaseConnection(HttpConnection conn) { - log.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)"); - - String host = conn.getHost(); - int port = conn.getPort(); - String key = host + ":" + port; - - if(log.isDebugEnabled()){ - log.debug("HttpConnectionManager.releaseConnection: Release connection for " + host + ":" + port); - } - - LinkedList listConnections = null; - synchronized(mapHosts){ - listConnections = (LinkedList)mapHosts.get(key); - if(listConnections == null){ - // This is an error, but we'll try to recover - log.error("HttpConnectionManager.releaseConnection: No connect list for " + key); - listConnections = new LinkedList(); - mapHosts.put(key, listConnections); - mapNumConnections.put(key, new Integer(1)); - } - } - - synchronized(listConnections){ - // Put the connect back in the available list and notify a waiter - listConnections.addFirst(conn); - listConnections.notify(); - } - } - - /** - * In getConnection, if the maximum number of connections has already - * been reached the call will block. This class is used to help provide - * a timeout facility for this wait. Because Java does not provide a way to - * determine if wait() returned due to a notify() or a timeout, we need - * an outside mechanism to interrupt the waiting thread after the specified - * timeout interval. - */ - private static class TimeoutThread extends Thread { - - private long timeout = 0; - private Thread thrdWakeup = null; - - public void setTimeout(long timeout) - { - this.timeout = timeout; - } - - public long getTimeout() - { - return timeout; - } - - public void setWakeupThread(Thread thrdWakeup) - { - this.thrdWakeup = thrdWakeup; - } - - public Thread getWakeupThread() - { - return thrdWakeup; - } - - public void run() { - log.trace("TimeoutThread.run()"); - if(timeout == 0){ - return; - } - if(thrdWakeup == null){ - return; - } + public void releaseConnection(HttpConnection conn); - try{ - sleep(timeout); - thrdWakeup.interrupt(); - }catch(InterruptedException e){ - log.debug("InterruptedException caught as expected"); - // This is expected - } - } - } } Index: java/org/apache/commons/httpclient/HttpMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethod.java,v retrieving revision 1.19 diff -u -r1.19 HttpMethod.java --- java/org/apache/commons/httpclient/HttpMethod.java 7 Sep 2002 01:07:47 -0000 1.19 +++ java/org/apache/commons/httpclient/HttpMethod.java 20 Nov 2002 14:43:02 -0000 @@ -303,6 +303,13 @@ public void recycle(); /** + * Releases the connection being used by this method. In particular the + * connection is used to read the response(if there is one) and will be held + * until the response has been read. + */ + public void releaseConnection(); + + /** * Use this method internally to add footers. * @since 2.0 */ Index: java/org/apache/commons/httpclient/HttpMethodBase.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.78 diff -u -r1.78 HttpMethodBase.java --- java/org/apache/commons/httpclient/HttpMethodBase.java 8 Nov 2002 23:14:30 -0000 1.78 +++ java/org/apache/commons/httpclient/HttpMethodBase.java 20 Nov 2002 14:43:07 -0000 @@ -64,23 +64,21 @@ import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.UnsupportedEncodingException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.OutputStream; -import java.net.URL; +import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; +import java.net.URL; import java.util.Date; -import java.util.Map; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; +import java.util.Map; import java.util.Set; import java.util.StringTokenizer; +import org.apache.commons.httpclient.util.URIUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.commons.httpclient.util.URIUtil; /** *

@@ -157,10 +155,10 @@ * @author dIon Gillard * @author Jeff Dever * @author Davanum Srinivas - * @author Ortwin Glück + * @author Ortwin Gl?ck */ public abstract class HttpMethodBase implements HttpMethod { - //~ Static variables/initializers ·········································· + //~ Static variables/initializers ?????????????????????????????????????????? /** Maximum number of redirects and authentications that will be followed */ private static int maxForwards = 100; @@ -182,7 +180,7 @@ USER_AGENT = new Header("User-Agent", agent); } - //~ Instance variables ····················································· + //~ Instance variables ????????????????????????????????????????????????????? /** My request headers, if any. */ private Map requestHeaders = new HashMap(); @@ -210,6 +208,9 @@ /** The response body, assuming it has not be intercepted by a sub-class. */ private InputStream responseStream = null; + + /** The connection that the response stream was read from. */ + private HttpConnection responseConnection = null; /** Buffer for the response */ private byte[] responseBody = null; @@ -242,7 +243,7 @@ protected static final String DEFAULT_CHARSET = "US-ASCII"; - //~ Constructors ··························································· + //~ Constructors ??????????????????????????????????????????????????????????? // ----------------------------------------------------------- Constructors @@ -267,7 +268,7 @@ } } - //~ Methods ································································ + //~ Methods ???????????????????????????????????????????????????????????????? // ------------------------------------------- Property Setters and Getters @@ -758,6 +759,15 @@ return false; } + private void wrapReponseStream( HttpConnection connection ) { + + if ( responseStream != null ) { + this.responseConnection = connection; + this.responseStream = new ResponseAutoReleaseInputStream(responseStream); + } + + } + /** * Execute this method. Note that we cannot currently support redirects * that change the connection parameters (host, port, protocol) because @@ -831,9 +841,11 @@ //if the authentication is successful, return the statusCode //otherwise, drop through the switch and try again. if (processAuthenticationResponse(state, conn)) { + wrapReponseStream(conn); return statusCode; } } else { //let the client handle the authenticaiton + wrapReponseStream(conn); return statusCode; } break; @@ -844,12 +856,14 @@ log.debug("Redirect required"); if (! processRedirectResponse(state, conn)) { + wrapReponseStream(conn); return statusCode; } break; default: // neither an unauthorized nor a redirect response + wrapReponseStream(conn); return statusCode; } //end of switch @@ -872,6 +886,8 @@ } } //end of loop + wrapReponseStream(conn); + log.error("Narrowly avoided an infinite loop in execute"); throw new HttpRecoverableException("Maximum redirects ("+ maxForwards +") exceeded"); } @@ -1027,6 +1043,8 @@ public void recycle() { log.trace("enter HttpMethodBase.recycle()"); + releaseConnection(); + path = null; followRedirects = false; doAuthentication = true; @@ -1041,6 +1059,19 @@ } /** + * @see org.apache.commons.httpclient.HttpMethod#releaseConnection() + */ + public void releaseConnection() { + + if ( responseConnection != null ) { + responseConnection.releaseConnection(); + this.responseConnection = null; + this.responseStream = null; + } + + } + + /** * Remove the request header associated with the given name. Note that * header-name matching is case insensitive. * @@ -1585,7 +1616,7 @@ responseBody = null; // is this desired? Header lengthHeader = getResponseHeader("Content-Length"); Header transferEncodingHeader = getResponseHeader("Transfer-Encoding"); - InputStream is = conn.getResponseInputStream(this); + InputStream is = conn.getResponseInputStream(); if (wireLog.isDebugEnabled()) { is = new WireLogInputStream(is); } @@ -1615,6 +1646,8 @@ try { int expectedLength = Integer.parseInt(lengthValue); + // FIXME: what if the content length is 0, perhaps we should + // just return an empty stream in that case result = new ContentLengthInputStream(is, expectedLength); } catch(NumberFormatException e) { throw new HttpException( @@ -1628,11 +1661,14 @@ && !getName().equals(ConnectMethod.NAME)){ result = is; } - if (result == null) return null; + if (result == null) { + return null; + } if (shouldCloseConnection()) { result = new AutoCloseInputStream(result, conn); } + return result; } @@ -2262,5 +2298,65 @@ return getContentCharSet(getResponseHeader("Content-Type")); } + /** + * Releases this connection from its connectionManager when the response has + * been read. + */ + private class ResponseAutoReleaseInputStream extends InputStream { + + private InputStream is; + + public ResponseAutoReleaseInputStream(InputStream is) { + this.is = is; + } + + /** + * @see java.io.InputStream#close() + */ + public void close() throws IOException { + is.close(); + releaseConnection(); + } + + /** + * @see java.io.InputStream#read() + */ + public int read() throws IOException { + int b = is.read(); + + if ( b == -1 ) { + releaseConnection(); + } + + return b; + } + + /** + * @see java.io.InputStream#read(byte, int, int) + */ + public int read(byte[] array, int off, int len) throws IOException { + int b = is.read(array, off, len); + + if ( b == -1 ) { + releaseConnection(); + } + + return b; + } + + /** + * @see java.io.InputStream#read(byte) + */ + public int read(byte[] array) throws IOException { + int b = is.read(array); + + if ( b == -1 ) { + releaseConnection(); + } + + return b; + } + + } } Index: java/org/apache/commons/httpclient/HttpMultiClient.java =================================================================== RCS file: java/org/apache/commons/httpclient/HttpMultiClient.java diff -N java/org/apache/commons/httpclient/HttpMultiClient.java --- java/org/apache/commons/httpclient/HttpMultiClient.java 16 Oct 2002 16:58:26 -0000 1.17 +++ /dev/null 1 Jan 1970 00:00:00 -0000 @@ -1,344 +0,0 @@ -/* - * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMultiClient.java,v 1.17 2002/10/16 16:58:26 jsdever Exp $ - * $Revision: 1.17 $ - * $Date: 2002/10/16 16:58:26 $ - * ==================================================================== - * - * The Apache Software License, Version 1.1 - * - * Copyright (c) 1999 The Apache Software Foundation. All rights - * reserved. - * - * Redistribution and use in source and binary forms, with or without - * modification, are permitted provided that the following conditions - * are met: - * - * 1. Redistributions of source code must retain the above copyright - * notice, this list of conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright - * notice, this list of conditions and the following disclaimer in - * the documentation and/or other materials provided with the - * distribution. - * - * 3. The end-user documentation included with the redistribution, if - * any, must include the following acknowlegement: - * "This product includes software developed by the - * Apache Software Foundation (http://www.apache.org/)." - * Alternately, this acknowlegement may appear in the software itself, - * if and wherever such third-party acknowlegements normally appear. - * - * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software - * Foundation" must not be used to endorse or promote products derived - * from this software without prior written permission. For written - * permission, please contact apache@apache.org. - * - * 5. Products derived from this software may not be called "Apache" - * nor may "Apache" appear in their names without prior written - * permission of the Apache Group. - * - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED - * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE - * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR - * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, - * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT - * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF - * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT - * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF - * SUCH DAMAGE. - * ==================================================================== - * - * 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 - * . - * - * [Additional notices, if required by prior licensing conditions] - * - */ - -package org.apache.commons.httpclient; - -import java.io.IOException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import javax.net.ssl.SSLSocketFactory; - -/** - * - * An Http user-agent that supports multiple connections - * to Http servers. - * - * @author Marc A. Saegesser - * @author Sean C. Sullivan - * - * @see HttpClient - * - */ -public class HttpMultiClient { - - // -------------------------------------------------------- Class Variables - - /** Log object for this class. */ - private static final Log log = LogFactory.getLog(HttpMultiClient.class); - - // ----------------------------------------------------- Instance Variables - /** cookies and other shared state */ - private HttpSharedState state = null; - /** manager of http connections on a host:port basis */ - private HttpConnectionManager mgr = new HttpConnectionManager(); - /** specifies strict HTTP compliance */ - private boolean strictMode = false; //experimental features off - /** how long to wait for a connection to become available */ - private int timeoutConnection = 0; - /** how long to wait for a request to complete */ - private int timeoutRequest = 0; - /** factory for ssl connections */ - private SSLSocketFactory sslSocketFactory = null; - - // ----------------------------------------------------------- Constructors - - /** - * No-args constructor. - * - * HttpMultiClient objects will have strictMode enabled by default. - * - */ - public HttpMultiClient() { - } - - /** - * Constructor that uses proxy host and port. Strict mode is enabled by - * default. - * - * @param proxyHost host name of the proxy server to use - * @param proxyPort port number of the proxy server to use - */ - public HttpMultiClient(String proxyHost, int proxyPort) { - this(); - mgr.setProxyHost(proxyHost); - mgr.setProxyPort(proxyPort); - } - - /** - * Set the shared state. - * - * @param state the new shared state - * - * @see #getState() - * - */ - public void setState(HttpSharedState state) { - this.state = state; - } - - /** - * Get the shared state - * - * @return the shared state - * - * @see #setState(HttpSharedState) - * - */ - public HttpSharedState getState() { - if (state == null) { - state = new HttpSharedState(); - } - - return state; - } - - /** - * - * @param strictMode true if strict mode should be used - * - * @see #isStrictMode() - * - */ - public void setStrictMode(boolean strictMode) { - this.strictMode = strictMode; - } - - /** - * - * @return true if strict mode being used - * - * @see #setStrictMode(boolean) - * - */ - public boolean isStrictMode() { - return strictMode; - } - - /** - * Sets the connection timeout. The connection timeout is how long (in - * milliseconds) HttpMultiClient will block in executeMethod waiting for - * a connection to the requested server to become available. - *
- * Setting the connection timeout to 0 will cause executeMethod() to - * block indefinitely (this is the default behaviour). - * - * @param timeout the time, in milliseconds, to block waiting for an - * available connection - * @see #getConnectionTimeout - * @see #executeMethod - */ - public void setConnectionTimeout(int timeout) { - this.timeoutConnection = timeout; - } - - /** - * Returns the value of connection timeout. - * - * @return the connection timeout value - * @see #setConnectionTimeout - */ - public int getConnectionTimeout() { - return timeoutConnection; - } - - /** - * Sets the request timeout. The executeMethod method calls - * HttpConnection.setSoTimeout() with this value before executing the - * request. - * - * @param timeout the SoTimeout value, in milliseconds - * @see #getRequestTimeout - * @see HttpConnection#setSoTimeout - */ - public void setRequestTimeout(int timeout) { - this.timeoutRequest = timeout; - } - - /** - * Returns the value of the request timeout. - * - * @return the request timeout - * @see #setRequestTimeout(int) - */ - public int getRequestTimeout() { - return timeoutRequest; - } - - /** - * Sets the hostname for the HTTP proxy server to use for all - * requests. - * - * @param host The hostname of the HTTP proxy server - * @see HttpConnectionManager#setProxyHost - */ - public void setProxyHost(String host) { - mgr.setProxyHost(host); - } - - /** - * Returns the hostname for the HTTP proxy server in use for - * all requests. - * - * @return the hostname of the HTTP proxy server - * @see HttpConnectionManager#getProxyHost - */ - public String getProxyHost() { - return mgr.getProxyHost(); - } - - /** - * Sets the port number for the HTTP proxy server to use - * for all requests. - * - * @param port the proxy server port number - * @see HttpConnectionManager#setProxyPort - */ - public void setProxyPort(int port) { - mgr.setProxyPort(port); - } - - /** - * Specifies an alternative factory for SSL sockets. - * @see HttpConnection#setSSLSocketFactory(SSLSocketFactory) HttpConnection - * setSSLSocketFactory - * @param sslSocketFactory a living instance of the alternative - * SSLSocketFactory - */ - public void setSSLSocketFactory(SSLSocketFactory sslSocketFactory) { - this.sslSocketFactory = sslSocketFactory; - } - - /** - * Returns the HTTP proxy server port number - * - * @return the HTTP proxy server port number - * @see HttpConnectionManager#getProxyPort - */ - public int getProxyPort() { - return mgr.getProxyPort(); - } - - /** - * - * Execute the given {@link HttpUrlMethod} using my current - * {@link HttpConnection connection} and {@link HttpState}. - * - * @param method the {@link HttpMethod} to execute. Must be non-null. - * @return the method's response code - * - * @throws IOException if an I/O error occurs - * @throws HttpException if a protocol exception occurs - * - */ - public int executeMethod(HttpUrlMethod method) - throws IOException, HttpException { - log.trace("enter executeMethod(HttpUrlMethod)"); - - if (null == method) { - throw new NullPointerException("method parameter"); - } - - HttpConnection connection = mgr.getConnection(method.getUrl(), - timeoutConnection); - connection.setSoTimeout(timeoutRequest); - if (!connection.isOpen()) { - connection.setSSLSocketFactory(sslSocketFactory); - } - - int status = 0; - - method.setStrictMode(strictMode); - - try { - status = method.execute(getState(), connection); - } catch (IOException ex) { - throw ex; // it might be IOException or HttpException instance. - } finally { - mgr.releaseConnection(connection); - } - - // FIXME: Why is this different from the redirect processing in - // HttpMethodBase - if (status == HttpStatus.SC_MOVED_PERMANENTLY - || status == HttpStatus.SC_MOVED_TEMPORARILY - || status == HttpStatus.SC_SEE_OTHER - || status == HttpStatus.SC_TEMPORARY_REDIRECT) { - Header header = method.getResponseHeader("Location"); - String url = header.getValue(); - if (url == null) { - log.error("HttpMultiClient.executeMethod: Received redirect " - + "without Location header."); - throw new HttpException("Received redirect without Location " - + "header."); - } - - method.recycle(); - method.setUrl(url); - return executeMethod(method); - } - - return status; - } - -} Index: java/org/apache/commons/httpclient/HttpState.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpState.java,v retrieving revision 1.12 diff -u -r1.12 HttpState.java --- java/org/apache/commons/httpclient/HttpState.java 5 Sep 2002 04:57:00 -0000 1.12 +++ java/org/apache/commons/httpclient/HttpState.java 20 Nov 2002 14:42:57 -0000 @@ -82,6 +82,7 @@ * @author Rodney Waldhoff * @author Jeff Dever * @author Sean C. Sullivan + * @author Michael Becke * * @version $Revision: 1.12 $ $Date: 2002/09/05 04:57:00 $ * @@ -105,11 +106,24 @@ */ private ArrayList cookies = new ArrayList(); + private HttpConnectionManager httpConnectionManager; + // -------------------------------------------------------- Class Variables /** Log object for this class. */ private static final Log log = LogFactory.getLog(HttpState.class); + /** + * Constructor for HttpState. + */ + public HttpState() { + + super(); + + this.httpConnectionManager = new SimpleHttpConnectionManager(); + + } + // ------------------------------------------------------------- Properties /** @@ -122,7 +136,7 @@ * @see #addCookies(Cookie[]) * */ - public void addCookie(Cookie cookie) { + public synchronized void addCookie(Cookie cookie) { log.trace("enter HttpState.addCookie(Cookie)"); if (cookie != null) { @@ -151,7 +165,7 @@ * * */ - public void addCookies(Cookie[] newcookies) { + public synchronized void addCookies(Cookie[] newcookies) { log.trace("enter HttpState.addCookies(Cookie[])"); if (newcookies != null) { @@ -169,7 +183,7 @@ * @see #getCookies(String, int, String, boolean, java.util.Date) * */ - public Cookie[] getCookies() { + public synchronized Cookie[] getCookies() { log.trace("enter HttpState.getCookies()"); return (Cookie[])(cookies.toArray(new Cookie[cookies.size()])); } @@ -189,7 +203,13 @@ * @see #getCookies() * */ - public Cookie[] getCookies(String domain, int port, String path, boolean secure, Date now) { + public synchronized Cookie[] getCookies( + String domain, + int port, + String path, + boolean secure, + Date now + ) { log.trace("enter HttpState.getCookies(String, int, String, boolean, Date)"); ArrayList list = new ArrayList(cookies.size()); @@ -211,7 +231,7 @@ * @see #purgeExpiredCookies(java.util.Date) * */ - public boolean purgeExpiredCookies() { + public synchronized boolean purgeExpiredCookies() { log.trace("enter HttpState.purgeExpiredCookies()"); return purgeExpiredCookies(new Date()); } @@ -224,7 +244,7 @@ * @see #purgeExpiredCookies() * */ - public boolean purgeExpiredCookies(Date date) { + public synchronized boolean purgeExpiredCookies(Date date) { log.trace("enter HttpState.purgeExpiredCookies(Date)"); boolean removed = false; Iterator it = cookies.iterator(); @@ -256,7 +276,7 @@ * @see #setProxyCredentials(String, Credentials) * */ - public void setCredentials(String realm, Credentials credentials) { + public synchronized void setCredentials(String realm, Credentials credentials) { log.trace("enter HttpState.setCredentials(String, Credentials)"); credMap.put(realm,credentials); } @@ -275,7 +295,7 @@ * @see #setCredentials(String, Credentials) * */ - public Credentials getCredentials(String realm) { + public synchronized Credentials getCredentials(String realm) { log.trace("enter HttpState.getCredentials(String)"); Credentials creds = (Credentials) credMap.get(realm); @@ -305,7 +325,7 @@ * @see #setCredentials(String, Credentials) * */ - public void setProxyCredentials(String realm, Credentials credentials) { + public synchronized void setProxyCredentials(String realm, Credentials credentials) { log.trace("enter HttpState.setProxyCredentials(String, credentials)"); proxyCred.put(realm, credentials); } @@ -322,7 +342,7 @@ * @return the credentials * @see #setProxyCredentials */ - public Credentials getProxyCredentials(String realm) { + public synchronized Credentials getProxyCredentials(String realm) { log.trace("enter HttpState.getProxyCredentials(String)"); Credentials creds = (Credentials) proxyCred.get(realm); if (creds == null) { @@ -331,7 +351,7 @@ return creds; } - public String toString() + public synchronized String toString() { StringBuffer sbResult = new StringBuffer(); @@ -401,4 +421,23 @@ } return sbResult; } + + /** + * Returns the httpConnectionManager. + * @return HttpConnectionManager + */ + public synchronized HttpConnectionManager getHttpConnectionManager() { + return httpConnectionManager; + } + + /** + * Sets the httpConnectionManager. + * @param httpConnectionManager The httpConnectionManager to set + */ + public synchronized void setHttpConnectionManager( + HttpConnectionManager httpConnectionManager + ) { + this.httpConnectionManager = httpConnectionManager; + } + } Index: java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java =================================================================== RCS file: java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java diff -N java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/MultiThreadedHttpConnectionManager.java 20 Nov 2002 14:43:01 -0000 @@ -0,0 +1,475 @@ +/* + * $Header: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpConnectionManager.java,v 1.11 2002/09/29 21:59:47 sullis Exp $ + * $Revision: 1.11 $ + * $Date: 2002/09/29 21:59:47 $ + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "HttpClient", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * 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 + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient; + +import java.lang.ref.Reference; +import java.lang.ref.ReferenceQueue; +import java.lang.ref.WeakReference; +import java.net.MalformedURLException; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.Map; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +/** + * Manages a set of HttpConnections for various host:ports. + * + * @author Marc A. Saegesser + * @author Michael Becke + */ +public class MultiThreadedHttpConnectionManager implements HttpConnectionManager { + + // -------------------------------------------------------- Class Variables + /** Log object for this class. */ + private static final Log log = LogFactory.getLog(MultiThreadedHttpConnectionManager.class); + + // ----------------------------------------------------- Instance Variables + private Map mapHosts = new HashMap(); + private int maxConnections = 2; // Per RFC 2616 sec 8.1.4 + + // mapping from reference to hostPort + private Map referenceToHostPort; + // the reference queue used to track when HttpConnections are lost to the + // garbage collector + private ReferenceQueue referenceQueue; + + /** + * No-args constructor + */ + public MultiThreadedHttpConnectionManager() { + + this.referenceToHostPort = Collections.synchronizedMap( new HashMap() ); + this.referenceQueue = new ReferenceQueue(); + + new ReferenceQueueThread().start(); + + } + + /** + * Set the maximum number of connections allowed for a given host:port. + * Per RFC 2616 section 8.1.4, this value defaults to 2. + * + * @param maxConnections - number of connections allowed for each host:port + */ + public void setMaxConnectionsPerHost(int maxConnections) { + this.maxConnections = maxConnections; + } + + /** + * Get the maximum number of connections allowed for a given host:port. + * + * @return The maximum number of connections allowed for a given host:port. + */ + public int getMaxConnectionsPerHost() { + return maxConnections; + } + + /** + * @see HttpConnectionManager#getConnection(HostConfiguration) + */ + public HttpConnection getConnection(HostConfiguration hostConfiguration) + throws MalformedURLException { + + while( true ) { + try { + return getConnection(hostConfiguration, 0); + } catch ( HttpException e ) { + log.debug( + "Unexpected exception while waiting for connection", + e + ); + }; + } + + } + + /** + * Return the port provided if not -1 (default), return 443 if the + * protocol is HTTPS, otherwise 80. + * + * This functionality is a URLUtil and may be better off in URIUtils + * + * @param protocol the protocol to use to get the port for, e.g. http or + * https + * @param port the port provided with the url (could be -1) + * @return the port for the specified port and protocol. + */ + private static int getPort(String protocol, int port) { + log.trace("HttpConnectionManager.getPort(String, port)"); + + // default to provided port + int portForProtocol = port; + if (portForProtocol == -1) { + if (protocol.equalsIgnoreCase("HTTPS")) { + portForProtocol = 443; + } else { + portForProtocol = 80; + } + } + return portForProtocol; + } + + /** + * @see HttpConnectionManager#getConnection(HostConfiguration, long) + */ + public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) + throws HttpException, MalformedURLException { + log.trace("enter HttpConnectionManager.getConnection(HostConfiguration, long)"); + + if (hostConfiguration == null) { + throw new MalformedURLException("hostConfiguration is null"); + } + + // Get the protocol and port (use default port if not specified) + String protocol = hostConfiguration.getProtocol(); + String host = hostConfiguration.getHost(); + int port = MultiThreadedHttpConnectionManager.getPort( + protocol, + hostConfiguration.getPort() + ); + String hostAndPort = host + ":" + port; + + if (log.isDebugEnabled()) { + log.debug("HttpConnectionManager.getConnection: key = " + + hostAndPort); + } + + // Look for a list of connections for the given host:port + HostConnectionPool connectionPool = getConnections(hostAndPort); + + HttpConnection conn = getConnection( + connectionPool, + host, + port, + protocol.equalsIgnoreCase("HTTPS"), + hostConfiguration.getProxyHost(), + hostConfiguration.getProxyPort(), + timeout + ); + + return conn; + } + + /** + * Gets a connection or waits if one is not available. A connection is + * available if one exists that is not being used or if fewer than + * maxConnections have been created in the connectionPool. + * + * @param connectionPool + * @param host + * @param port + * @param useHttps + * @param proxyHost + * @param proxyPort + * @param timeout the number of milliseconds to wait for a connection, 0 to + * wait indefinitely + * + * @return HttpConnection an available connection + * + * @throws HttpException if a connection does not available in 'timeout' + * milliseconds + */ + private HttpConnection getConnection( + HostConnectionPool connectionPool, + String host, + int port, + boolean useHttps, + String proxyHost, + int proxyPort, + long timeout + ) throws HttpException { + + HttpConnection connection = null; + + synchronized( connectionPool ) { + + // keep trying until a connection is available, should happen at + // most twice + while ( connection == null ) { + + if (connectionPool.freeConnections.size() > 0) { + connection = (HttpConnection)connectionPool.freeConnections.removeFirst(); + } else { + // get number of connections to host:port + if (connectionPool.numConnections < maxConnections) { + // Create a new connection + connection = new HttpConnection( + proxyHost, + proxyPort, + host, + port, + useHttps + ); + connection.setHttpConnectionManager( this ); + connectionPool.numConnections++; + + // add a weak reference to this connection + referenceToHostPort.put( + new WeakReference( connection, referenceQueue ), + host + ":" + port + ); + + } else { + + TimeoutThread threadTimeout = new TimeoutThread(); + threadTimeout.setTimeout(timeout); + threadTimeout.setWakeupThread(Thread.currentThread()); + threadTimeout.start(); + + try { + log.debug( + "HttpConnectionManager.getConnection: waiting for " + + "connection from " + connectionPool + ); + connectionPool.wait(); + // we were woken up before the timeout occurred, so + // there should be a connection available + threadTimeout.interrupt(); + } catch (InterruptedException e) { + throw new HttpException("Timeout waiting for connection."); + } + + } + } + } + + } + + return connection; + } + + /** + * Get the pool (list) of connections available for the given host and port + * + * @param hostAndPort the key for the connection pool + * @return a pool (list) of connections available for the given key + */ + private HostConnectionPool getConnections(String hostAndPort) { + log.trace("enter HttpConnectionManager.getConnections(String)"); + + // Look for a list of connections for the given host:port + HostConnectionPool listConnections = null; + synchronized (mapHosts) { + listConnections = (HostConnectionPool) mapHosts.get(hostAndPort); + if (listConnections == null) { + // First time for this host:port + listConnections = new HostConnectionPool(); + mapHosts.put(hostAndPort, listConnections); + } + } + return listConnections; + } + + /** + * Get the number of connections in use for the key + * + * @param hostAndPort the key that connections are tracked on + * @return the number of connections in use for the given key + */ + public int getConnectionsInUse(String hostAndPort) { + log.trace("enter HttpConnectionManager.getConnectionsInUse(String)"); + + HostConnectionPool connectionPool = getConnections(hostAndPort); + synchronized( connectionPool ) { + return connectionPool.numConnections; + } + + } + + /** + * Make the given HttpConnection available for use by other requests. + * If another thread is blocked in getConnection() waiting for a connection + * for this host:port, they will be woken up. + * + * @param conn - The HttpConnection to make available. + */ + public void releaseConnection(HttpConnection conn) { + log.trace("enter HttpConnectionManager.releaseConnection(HttpConnection)"); + + String host = conn.getHost(); + int port = conn.getPort(); + String key = host + ":" + port; + + if(log.isDebugEnabled()){ + log.debug("HttpConnectionManager.releaseConnection: Release connection for " + host + ":" + port); + } + + HostConnectionPool listConnections = getConnections(key); + synchronized(listConnections){ + // Put the connect back in the available list and notify a waiter + listConnections.freeConnections.addFirst(conn); + if ( listConnections.numConnections == 0 ) { + // for some reason this connection pool didn't already exist + log.error("connection pool not found for host: " + key); + listConnections.numConnections = 1; + } + listConnections.notify(); + } + } + + /** + * A simple struct-link class to combine the connection list and the count + * of created connections. + */ + private class HostConnectionPool { + + public LinkedList freeConnections = new LinkedList(); + public int numConnections = 0; + + } + + /** + * A thread for listening for HttpConnections reclaimed by the garbage + * collector. + */ + private class ReferenceQueueThread extends Thread { + + public ReferenceQueueThread() { + setDaemon(true); + } + + /** + * @see java.lang.Runnable#run() + */ + public void run() { + + while(true) { + + try { + Reference ref = referenceQueue.remove(); + + if ( ref != null ) { + String hostPort = (String)referenceToHostPort.get(ref); + referenceToHostPort.remove(ref); + HostConnectionPool connectionPool = getConnections(hostPort); + synchronized( connectionPool ) { + connectionPool.numConnections--; + connectionPool.notify(); + } + } + } catch (InterruptedException e) { + log.debug("ReferenceQueueThread interrupted", e); + } + + } + + } + + } + + /** + * In getConnection, if the maximum number of connections has already + * been reached the call will block. This class is used to help provide + * a timeout facility for this wait. Because Java does not provide a way to + * determine if wait() returned due to a notify() or a timeout, we need + * an outside mechanism to interrupt the waiting thread after the specified + * timeout interval. + */ + private static class TimeoutThread extends Thread { + + private long timeout = 0; + private Thread thrdWakeup = null; + + public void setTimeout(long timeout) + { + this.timeout = timeout; + } + + public long getTimeout() + { + return timeout; + } + + public void setWakeupThread(Thread thrdWakeup) + { + this.thrdWakeup = thrdWakeup; + } + + public Thread getWakeupThread() + { + return thrdWakeup; + } + + public void run() { + log.trace("TimeoutThread.run()"); + if(timeout == 0){ + return; + } + if(thrdWakeup == null){ + return; + } + + try{ + sleep(timeout); + thrdWakeup.interrupt(); + }catch(InterruptedException e){ + log.debug("InterruptedException caught as expected"); + // This is expected + } + } + } + +} Index: java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java =================================================================== RCS file: java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java diff -N java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 20 Nov 2002 14:43:02 -0000 @@ -0,0 +1,81 @@ +package org.apache.commons.httpclient; + +import java.net.MalformedURLException; + +/** + */ +public class SimpleHttpConnectionManager implements HttpConnectionManager { + + private HttpConnection httpConnection; + + /** + * Constructor for SimpleHttpConnectionManager. + */ + public SimpleHttpConnectionManager() { + super(); + } + + /** + * @see org.apache.commons.httpclient.HttpConnectionManager#getConnection(HostConfiguration) + */ + public HttpConnection getConnection(HostConfiguration hostConfiguration) + throws MalformedURLException { + + return getConnection(hostConfiguration, 0); + } + + /** + * @see org.apache.commons.httpclient.HttpConnectionManager#getConnection(HostConfiguration, long) + */ + public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout) + throws MalformedURLException { + + String protocol = hostConfiguration.getProtocol(); + String host = hostConfiguration.getHost(); + int port = hostConfiguration.getPort(); + boolean isSecure = protocol.equalsIgnoreCase("HTTPS"); + + if ( httpConnection == null ) { + + if ( hostConfiguration.isProxySet() ) { + httpConnection = new HttpConnection( + hostConfiguration.getProxyHost(), + hostConfiguration.getProxyPort(), + host, + port, + isSecure + ); + } else { + httpConnection = new HttpConnection(host, port, isSecure); + } + + } else { + + // FIXME: do we really want to close this + // maybe we only should if it is a different host + if ( httpConnection.isOpen() ) { + httpConnection.close(); + } + + httpConnection.setHost(host); + httpConnection.setPort(port); + httpConnection.setSecure(isSecure); + + if ( hostConfiguration.isProxySet() ) { + httpConnection.setProxyHost(hostConfiguration.getProxyHost()); + httpConnection.setProxyPort(hostConfiguration.getProxyPort()); + } + + } + + return httpConnection; + + } + + /** + * @see org.apache.commons.httpclient.HttpConnectionManager#releaseConnection(org.apache.commons.httpclient.HttpConnection) + */ + public void releaseConnection(HttpConnection conn) { + } + +} Index: test/org/apache/commons/httpclient/TestHttpClientLocalHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpClientLocalHost.java,v retrieving revision 1.3 diff -u -r1.3 TestHttpClientLocalHost.java --- test/org/apache/commons/httpclient/TestHttpClientLocalHost.java 1 Nov 2002 09:51:05 -0000 1.3 +++ test/org/apache/commons/httpclient/TestHttpClientLocalHost.java 20 Nov 2002 14:42:56 -0000 @@ -78,14 +78,10 @@ * @author Rodney Waldhoff * @version $Id: TestHttpClientLocalHost.java,v 1.3 2002/11/01 09:51:05 oglueck Exp $ */ -public class TestHttpClientLocalHost extends TestCase { +public class TestHttpClientLocalHost extends TestLocalHostBase { // -------------------------------------------------------------- Constants - - - private static final String host = "127.0.0.1"; - private static final int port = 8080; // ------------------------------------------------------------ Constructor Index: test/org/apache/commons/httpclient/TestHttpConnection.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpConnection.java,v retrieving revision 1.2 diff -u -r1.2 TestHttpConnection.java --- test/org/apache/commons/httpclient/TestHttpConnection.java 12 Nov 2002 09:58:23 -0000 1.2 +++ test/org/apache/commons/httpclient/TestHttpConnection.java 20 Nov 2002 14:42:57 -0000 @@ -76,9 +76,9 @@ * @version $Id: $ * */ -public class TestHttpConnection extends TestCase { +public class TestHttpConnection extends TestLocalHostBase { // ----------------------------------------------------- Instance Variables - + // ------------------------------------------------------------ Constructor public TestHttpConnection(String testName) { super(testName); @@ -100,13 +100,13 @@ // ----------------------------------------------------------- Test Methods public void testConstructThenClose() { - HttpConnection conn = new HttpConnection("localhost", 8080); + HttpConnection conn = new HttpConnection(host,port); conn.close(); assertTrue( ! conn.isOpen() ); } public void testConnTimeout() { - HttpConnection conn = new HttpConnection("localhost", 8080); + HttpConnection conn = new HttpConnection(host,port); // 1 ms is short enough to make this fail conn.setConnectionTimeout(1); try { @@ -118,9 +118,8 @@ } } - public void testForIllegalStateExceptions() { - HttpConnection conn = new HttpConnection("localhost", 8080); + HttpConnection conn = new HttpConnection(host,port); try { OutputStream out = conn.getRequestOutputStream(); @@ -134,7 +133,7 @@ } try { - OutputStream out = conn.getRequestOutputStream(true); + OutputStream out = new ChunkedOutputStream(conn.getRequestOutputStream()); fail("getRequestOutputStream(true) did not throw the expected exception"); } catch (IllegalStateException expected) { @@ -145,7 +144,7 @@ } try { - InputStream in = conn.getResponseInputStream(new PostMethod()); + InputStream in = conn.getResponseInputStream(); fail("getResponseInputStream() did not throw the expected exception"); } catch (IllegalStateException expected) { Index: test/org/apache/commons/httpclient/TestHttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestHttpConnectionManager.java,v retrieving revision 1.1 diff -u -r1.1 TestHttpConnectionManager.java --- test/org/apache/commons/httpclient/TestHttpConnectionManager.java 12 Apr 2002 21:17:06 -0000 1.1 +++ test/org/apache/commons/httpclient/TestHttpConnectionManager.java 20 Nov 2002 14:42:56 -0000 @@ -62,11 +62,13 @@ package org.apache.commons.httpclient; -import junit.framework.*; +import java.io.IOException; import java.net.MalformedURLException; -import java.lang.reflect.*; -import org.apache.commons.httpclient.methods.*; +import junit.framework.Test; +import junit.framework.TestSuite; + +import org.apache.commons.httpclient.methods.GetMethod; /** * @@ -76,7 +78,7 @@ * @author Marc A. Saegesser * @version $Id: TestHttpConnectionManager.java,v 1.1 2002/04/12 21:17:06 marcsaeg Exp $ */ -public class TestHttpConnectionManager extends TestCase { +public class TestHttpConnectionManager extends TestLocalHostBase { // ----------------------------------------------------- Instance Variables // ------------------------------------------------------------ Constructor @@ -100,22 +102,8 @@ // ----------------------------------------------------------- Test Methods // Test the accessor methods - public void testProxyHostAccessors() { - HttpConnectionManager mgr = new HttpConnectionManager(); - - mgr.setProxyHost("proxyhost"); - assertEquals("Proxy Host", "proxyhost", mgr.getProxyHost()); - } - - public void testProxyPortAccessors() { - HttpConnectionManager mgr = new HttpConnectionManager(); - - mgr.setProxyPort(8888); - assertEquals("Proxy Port", 8888, mgr.getProxyPort()); - } - public void testMaxConnectionsAccessors() { - HttpConnectionManager mgr = new HttpConnectionManager(); + MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager(); // First test the default value assertEquals("Default MaxConnections", 2, mgr.getMaxConnectionsPerHost()); @@ -125,11 +113,14 @@ } public void testGetConnection() { - HttpConnectionManager mgr = new HttpConnectionManager(); + MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager(); + + HostConfiguration hostConfiguration = new HostConfiguration(); + hostConfiguration.setHost("www.nosuchserver.com", 80, "http"); try{ // Create a new connection - HttpConnection conn = mgr.getConnection("http://www.nosuchserver.com/path/path?query=string"); + HttpConnection conn = mgr.getConnection(hostConfiguration); // Validate the connection properties assertEquals("Host", "www.nosuchserver.com", conn.getHost()); assertEquals("Port", 80, conn.getPort()); @@ -137,7 +128,8 @@ mgr.releaseConnection(conn); // Create a new connection - conn = mgr.getConnection("https://www.nosuchserver.com/path/path?query=string"); + hostConfiguration.setHost("www.nosuchserver.com", -1, "https"); + conn = mgr.getConnection(hostConfiguration); // Validate the connection properties assertEquals("Host", "www.nosuchserver.com", conn.getHost()); assertEquals("Port", 443, conn.getPort()); @@ -145,7 +137,8 @@ mgr.releaseConnection(conn); // Create a new connection - conn = mgr.getConnection("http://www.nowhere.org:8080/path/path?query=string"); + hostConfiguration.setHost("www.nowhere.org", 8080, "http"); + conn = mgr.getConnection(hostConfiguration); // Validate the connection properties assertEquals("Host", "www.nowhere.org", conn.getHost()); assertEquals("Port", 8080, conn.getPort()); @@ -154,47 +147,209 @@ }catch(MalformedURLException e){ fail("Caught unexpected MalformedURLException (" + e.toString() + ")"); - }catch(HttpException e){ - fail("Caught unexpected HttpException (" + e.toString() + ")"); } + } + + public void testReleaseConnection() { + + MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.setMaxConnectionsPerHost(1); + + HttpClient client = new HttpClient(connectionManager); + client.getHostConfiguration().setHost(host, port, "http"); + // we shouldn't have to wait if a connection is available + client.setHttpConnectionFactoryTimeout( 1 ); + + GetMethod getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + try { + client.executeMethod(getMethod); + } catch (Exception e) { + fail("error reading from server: " + e); + } + + try { + // this should fail quickly since the connection has not been released + client.executeMethod(getMethod); + fail("a httpConnection should not be available"); + } catch (HttpException e) { + } catch (IOException e) { + fail("error reading from server; " + e); + } + + // this should release the connection + getMethod.releaseConnection(); + + getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + try { + // this should fail quickly if the connection has not been released + client.executeMethod(getMethod); + } catch (HttpException e) { + fail("httpConnection does not appear to have been released: " + e); + } catch (IOException e) { + fail("error reading from server; " + e); + } } + /** + * Makes sure that a connection gets released after the content of the body + * is read. + */ + public void testResponseAutoRelease() { + + MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.setMaxConnectionsPerHost(1); + + HttpClient client = new HttpClient(connectionManager); + client.getHostConfiguration().setHost(host, port, "http"); + // we shouldn't have to wait if a connection is available + client.setHttpConnectionFactoryTimeout( 1 ); + + GetMethod getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + try { + client.executeMethod(getMethod); + } catch (Exception e) { + fail("error reading from server: " + e); + } + + // this should release the connection + getMethod.getResponseBody(); + + getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + try { + // this should fail quickly if the connection has not been released + client.executeMethod(getMethod); + } catch (HttpException e) { + fail("httpConnection does not appear to have been released: " + e); + } catch (IOException e) { + fail("error reading from server; " + e); + } + + } + + public void testMaxConnectionsPerServer() { + + MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.setMaxConnectionsPerHost(1); + + HttpClient client = new HttpClient(connectionManager); + client.getHostConfiguration().setHost(host, port, "http"); + // we shouldn't have to wait if a connection is available + client.setHttpConnectionFactoryTimeout( 1 ); + + GetMethod getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + try { + client.executeMethod(getMethod); + } catch (Exception e) { + fail("error reading from server: " + e); + } + + GetMethod getMethod2 = new GetMethod("/"); + getMethod2.setFollowRedirects(true); + + try { + // this should fail quickly since the connection has not been released + client.executeMethod(getMethod2); + fail("a httpConnection should not be available"); + } catch (HttpException e) { + } catch (IOException e) { + fail("error reading from server; " + e); + } + + } + + public void testReclaimUnusedConnection() { + + MultiThreadedHttpConnectionManager connectionManager = new MultiThreadedHttpConnectionManager(); + connectionManager.setMaxConnectionsPerHost(1); + + HttpClient client = new HttpClient(connectionManager); + client.getHostConfiguration().setHost(host, port, "http"); + // we shouldn't have to wait if a connection is available + client.setHttpConnectionFactoryTimeout( 30000 ); + + GetMethod getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + try { + client.executeMethod(getMethod); + } catch (Exception e) { + fail("error reading from server: " + e); + } + + getMethod = new GetMethod("/"); + getMethod.setFollowRedirects(true); + + Runtime.getRuntime().gc(); + + try { + // we didn't explicitly release the connection, but it should be + // reclaimed by the garbage collector, we hope:) + client.executeMethod(getMethod); + } catch (HttpException e) { + fail("httpConnection does not appear to have been reclaimed by the GC: " + e); + } catch (IOException e) { + fail("error reading from server; " + e); + } + + } + public void testGetMultipleConnections() { - HttpConnectionManager mgr = new HttpConnectionManager(); + HttpConnectionManager mgr = new MultiThreadedHttpConnectionManager(); + + HostConfiguration hostConfig1 = new HostConfiguration(); + hostConfig1.setHost("www.nosuchserver.com", 80, "http"); + + HostConfiguration hostConfig2 = new HostConfiguration(); + hostConfig2.setHost("www.nosuchserver.com", -1, "http"); + + HostConfiguration hostConfig3 = new HostConfiguration(); + hostConfig3.setHost("www.nosuchserver.com", -1, "http"); try{ // Create a new connection - HttpConnection conn1 = mgr.getConnection("http://www.nosuchserver.com/path/path?query=string"); + HttpConnection conn1 = mgr.getConnection(hostConfig1); // Release the connection mgr.releaseConnection(conn1); // Get the same connection again - HttpConnection conn2 = mgr.getConnection("http://www.nosuchserver.com/"); + HttpConnection conn2 = mgr.getConnection(hostConfig2); assertEquals("Same connection", conn1, conn2); // don't release yet // Get another new connection - HttpConnection conn3 = mgr.getConnection("http://www.nosuchserver.com/"); + HttpConnection conn3 = mgr.getConnection(hostConfig3); assertTrue(conn2 != conn3); }catch(MalformedURLException e){ fail("Caught unexpected MalformedURLException (" + e.toString() + ")"); - }catch(HttpException e){ - fail("Caught unexpected HttpException (" + e.toString() + ")"); } } public void testTimeout() { - HttpConnectionManager mgr = new HttpConnectionManager(); + MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager(); try{ - HttpConnection conn1 = mgr.getConnection("http://www.nosuchserver.com/path/path?query=string"); - HttpConnection conn2 = mgr.getConnection("http://www.nosuchserver.com/path/path?query=string"); - HttpConnection conn3 = mgr.getConnection("http://www.nosuchserver.com/path/path?query=string", 5000); + HostConfiguration hostConfig = new HostConfiguration(); + hostConfig.setHost("www.nosuchserver.com", 80, "http"); + + HttpConnection conn1 = mgr.getConnection(hostConfig); + HttpConnection conn2 = mgr.getConnection(hostConfig); + HttpConnection conn3 = mgr.getConnection(hostConfig, 5000); fail("Expected an HttpException."); + }catch(MalformedURLException e){ fail("Caught unexpected MalformedURLException (" + e.toString() + ")"); }catch(HttpException e){ Index: test/org/apache/commons/httpclient/TestLocalHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestLocalHost.java,v retrieving revision 1.4 diff -u -r1.4 TestLocalHost.java --- test/org/apache/commons/httpclient/TestLocalHost.java 3 Sep 2002 01:24:52 -0000 1.4 +++ test/org/apache/commons/httpclient/TestLocalHost.java 20 Nov 2002 14:42:55 -0000 @@ -81,6 +81,7 @@ public static Test suite() { TestSuite suite = new TestSuite(); suite.addTest(TestHttpClientLocalHost.suite()); + suite.addTest(TestHttpConnection.suite()); suite.addTest(TestMethodsLocalHost.suite()); suite.addTest(TestGetMethodLocal.suite()); suite.addTest(TestTraceMethodLocal.suite()); Index: test/org/apache/commons/httpclient/TestLocalHostBase.java =================================================================== RCS file: test/org/apache/commons/httpclient/TestLocalHostBase.java diff -N test/org/apache/commons/httpclient/TestLocalHostBase.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ test/org/apache/commons/httpclient/TestLocalHostBase.java 20 Nov 2002 14:42:56 -0000 @@ -0,0 +1,92 @@ +/* + * $Header: $ + * $Revision: $ + * $Date: $ + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2002 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, if + * any, must include the following acknowlegement: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowlegement may appear in the software itself, + * if and wherever such third-party acknowlegements normally appear. + * + * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software + * Foundation" must not be used to endorse or promote products derived + * from this software without prior written permission. For written + * permission, please contact apache@apache.org. + * + * 5. Products derived from this software may not be called "Apache" + * nor may "Apache" appear in their names without prior written + * permission of the Apache Group. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * 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 + * . + * + * [Additional notices, if required by prior licensing conditions] + * + */ + +package org.apache.commons.httpclient; + +import junit.framework.TestCase; + +/** + */ +public abstract class TestLocalHostBase extends TestCase { + + /** + * Constructor for TestLocalHostBase. + * @param testName + */ + public TestLocalHostBase(String testName) { + super(testName); + } + + protected static final String host = System.getProperty("httpclient.test.localHost","localhost"); + protected static final int port; + static { + String portString = System.getProperty("httpclient.test.localPort","8080"); + int tempPort = 8080; + try { + tempPort = Integer.parseInt(portString); + } catch(Exception e) { + tempPort = 8080; + } + port = tempPort; + } + +}