Index: C:/depot/Tools/commons-httpclient-trunk/src/java/org/apache/commons/httpclient/HttpClient.java =================================================================== --- C:/depot/Tools/commons-httpclient-trunk/src/java/org/apache/commons/httpclient/HttpClient.java (revision 509010) +++ C:/depot/Tools/commons-httpclient-trunk/src/java/org/apache/commons/httpclient/HttpClient.java (working copy) @@ -383,7 +383,7 @@ URI uri = method.getURI(); if (hostconfig == defaulthostconfig || uri.isAbsoluteURI()) { // make a deep copy of the host defaults - hostconfig = new HostConfiguration(hostconfig); + hostconfig = (HostConfiguration) hostconfig.clone(); if (uri.isAbsoluteURI()) { hostconfig.setHost(uri); } Index: C:/depot/Tools/commons-httpclient-trunk/src/test/org/apache/commons/httpclient/TestHostConfiguration.java =================================================================== --- C:/depot/Tools/commons-httpclient-trunk/src/test/org/apache/commons/httpclient/TestHostConfiguration.java (revision 509010) +++ C:/depot/Tools/commons-httpclient-trunk/src/test/org/apache/commons/httpclient/TestHostConfiguration.java (working copy) @@ -160,4 +160,73 @@ } } + /** + * Test that HttpClient uses HostConfiguration.clone (not the copy + * constructor) to copy its default HostConfiguration when preparing to + * execute a method. This behavior is required to support specialized + * Protocols; for example, HostConfigurationWithStickyProtocol. + * + * @see org.apache.commons.httpclient.contrib.ssl.HostConfigurationWithStickyProtocol + */ + public void testClientClonesHostConfiguration() throws IOException { + this.server.setHttpService(new EchoService()); + SpecialHostConfiguration configuration = new SpecialHostConfiguration(this.client + .getHostConfiguration()); + configuration.setHost(this.server.getLocalAddress(), this.server.getLocalPort(), + new String(HttpURL.DEFAULT_SCHEME)); + this.client.setHostConfiguration(configuration); + + HttpMethod method = new GetMethod(configuration.getHostURL() + "/test/"); + try { + this.client.executeMethod(method); + fail("HostConfiguration wasn't cloned"); + } catch (ExpectedError e) { + assertNotSame("ExpectedError.configuration", configuration, e.configuration); + } finally { + method.releaseConnection(); + } + + method = new GetMethod("/test/"); + try { + this.client.executeMethod(method); + fail("HostConfiguration wasn't cloned"); + } catch (ExpectedError e) { + assertNotSame("ExpectedError.configuration", configuration, e.configuration); + } finally { + method.releaseConnection(); + } + } + + /** A HostConfiguration that refuses to provide a protocol. */ + private class SpecialHostConfiguration extends HostConfiguration + { + SpecialHostConfiguration(HostConfiguration hostConfiguration) + { + super(hostConfiguration); + } + + public Object clone() + { + return new SpecialHostConfiguration(this); + } + + public synchronized Protocol getProtocol() + { + throw new ExpectedError(this); + } + } + + private class ExpectedError extends Error + { + ExpectedError(SpecialHostConfiguration c) + { + configuration = c; + } + + SpecialHostConfiguration configuration; + + private static final long serialVersionUID = 1L; + + } + } Index: C:/depot/Tools/commons-httpclient-trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/HostConfigurationWithStickyProtocol.java =================================================================== --- C:/depot/Tools/commons-httpclient-trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/HostConfigurationWithStickyProtocol.java (revision 0) +++ C:/depot/Tools/commons-httpclient-trunk/src/contrib/org/apache/commons/httpclient/contrib/ssl/HostConfigurationWithStickyProtocol.java (revision 0) @@ -0,0 +1,69 @@ +package org.apache.commons.httpclient.contrib.ssl; + +import org.apache.commons.httpclient.HostConfiguration; +import org.apache.commons.httpclient.HttpHost; +import org.apache.commons.httpclient.protocol.Protocol; + +/** + * A kind of HostConfiguration that can retain its Protocol when its host name + * or port changes. HttpClient may clone its HostConfigurationWithStickyProtocol + * and change the host URL, without changing the specialized Protocol. + *

+ * This is useful for integrating a specialized Protocol or SocketFactory; for + * example, a SecureSocketFactory that authenticates via SSL. Use + * HttpClient.setHostConfiguration to install a + * HostConfigurationWithStickyProtocol that contains the specialized Protocol or + * SocketFactory. + *

+ * An alternative is to use Protocol.registerProtocol to register a specialized + * Protocol. But that has drawbacks: it makes it hard to integrate modules (e.g. + * web applications in a servlet container) with different strategies, because + * they share the specialized Protocol (Protocol.PROTOCOLS is static). Also, it + * can't handle multiple socket factories for the same host and port, since the + * URL path isn't a parameter to Protocol.getProtocol or socket factory methods. + * + * @author John Kristian + */ +public class HostConfigurationWithStickyProtocol extends HostConfiguration +{ + public HostConfigurationWithStickyProtocol() + { + } + + public HostConfigurationWithStickyProtocol(HostConfiguration hostConfiguration) + { + super(hostConfiguration); + } + + public Object clone() + { + return new HostConfigurationWithStickyProtocol(this); + } + + public synchronized void setHost(String host, int port, String scheme) + { + setHost(new HttpHost(host, port, getNewProtocol(host, port, scheme))); + } + + /** + * Select a Protocol to be used for the given host, port and scheme. The + * current Protocol may be selected, if appropriate. This method need not be + * thread-safe; the caller must synchronize if necessary. + *

+ * This implementation returns the current Protocol if it has the given + * scheme; otherwise it returns the Protocol registered for that scheme. + */ + protected Protocol getNewProtocol(String host, int port, String scheme) + { + final Protocol oldProtocol = getProtocol(); + if (oldProtocol != null) { + final String oldScheme = oldProtocol.getScheme(); + if (oldScheme == scheme || (oldScheme != null && oldScheme.equalsIgnoreCase(scheme))) { + // The old {rotocol has the desired scheme. + return oldProtocol; // Retain it. + } + } + return Protocol.getProtocol(scheme); + } + +}