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 508748) +++ 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/java/org/apache/commons/httpclient/HostProtocolConfiguration.java =================================================================== --- C:/depot/Tools/commons-httpclient-trunk/src/java/org/apache/commons/httpclient/HostProtocolConfiguration.java (revision 0) +++ C:/depot/Tools/commons-httpclient-trunk/src/java/org/apache/commons/httpclient/HostProtocolConfiguration.java (revision 0) @@ -0,0 +1,63 @@ +package org.apache.commons.httpclient; + +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 HostProtocolConfiguration and + * change the host URL, without changing the specialized socket factory. + *
+ * This is useful for integrating a specialized Protocol or SocketFactory; for + * example, a SecureSocketFactory that authenticates via SSL. Use + * HttpClient.setHostConfiguration to install a HostProtocolConfiguration that + * contains the specialized Protocol or SocketFactory. For example, see + * TestHostConfiguration.testHostProcoolConfiguration. + *
+ * 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). + */ +public class HostProtocolConfiguration extends HostConfiguration +{ + public HostProtocolConfiguration() + { + } + + public HostProtocolConfiguration(HostConfiguration hostConfiguration) + { + super(hostConfiguration); + } + + public Object clone() + { + return new HostProtocolConfiguration(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); + } + +} 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 508748) +++ C:/depot/Tools/commons-httpclient-trunk/src/test/org/apache/commons/httpclient/TestHostConfiguration.java (working copy) @@ -29,13 +29,17 @@ package org.apache.commons.httpclient; import java.io.IOException; +import java.net.InetAddress; +import java.net.Socket; import java.net.UnknownHostException; import junit.framework.Test; import junit.framework.TestSuite; import org.apache.commons.httpclient.methods.GetMethod; +import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.Protocol; +import org.apache.commons.httpclient.protocol.ProtocolSocketFactory; import org.apache.commons.httpclient.server.SimpleProxy; /** @@ -160,4 +164,66 @@ } } + /** + * Test that a specialized socket factory, supplied in a + * HostProtocolConfiguration, is used to execute a method with an absolute + * URI with the same scheme as the Protocol. + */ + public void testHostProtocolConfiguration() throws IOException { + /** + * The intended use involves a 'good' factory in the HostConfiguration + * and a 'bad' factory registered with Protocol. But this test does the + * reverse, to avoid interfering with other test cases (by registering a + * bad factory). That is, the HostConfiguration contains a BadFactory, + * and this test expects a 'bad' outcome. + */ + this.server.setHttpService(new EchoService()); + this.client.setHostConfiguration(new HostProtocolConfiguration(this.client.getHostConfiguration())); + Protocol protocol = new Protocol(new String(HttpURL.DEFAULT_SCHEME), new BadFactory(), HttpURL.DEFAULT_PORT); + HttpHost host = new HttpHost(this.server.getLocalAddress(), this.server.getLocalPort(), protocol); + this.client.getHostConfiguration().setHost(host); + final GetMethod method = new GetMethod(host.toURI() + "/test/"); + try { + this.client.executeMethod(method); + fail("HostConfiguration.host.protocol.socketFactory was ignored"); + } catch (IOException actual) { + assertEquals("IOException message", BadFactory.MESSAGE, actual.getMessage()); + } finally { + method.releaseConnection(); + } + // Next, try a different scheme. BadFactory should not be used. + protocol = new Protocol(new String(HttpsURL.DEFAULT_SCHEME), new BadFactory(), HttpsURL.DEFAULT_PORT); + host = new HttpHost(host.getHostName(), host.getPort(), protocol); + this.client.getHostConfiguration().setHost(host); + try { + this.client.executeMethod(method); + assertEquals("HTTP status", HttpStatus.SC_OK, method.getStatusCode()); + } finally { + method.releaseConnection(); + } + } + + /** A silly factory that merely throws exceptions. */ + public static class BadFactory implements ProtocolSocketFactory { + + public static final String MESSAGE = "bad factory; no biscuit"; + + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort, + HttpConnectionParams params) throws IOException, UnknownHostException, + ConnectTimeoutException + { + throw new IOException(MESSAGE); + } + + public Socket createSocket(String host, int port, InetAddress localAddress, int localPort) + throws IOException, UnknownHostException + { + throw new IOException(MESSAGE); + } + + public Socket createSocket(String host, int port) throws IOException, UnknownHostException + { + throw new IOException(MESSAGE); + } + } }