Index: java/org/apache/commons/httpclient/ConnectMethod.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ConnectMethod.java,v retrieving revision 1.20 diff -u -r1.20 ConnectMethod.java --- java/org/apache/commons/httpclient/ConnectMethod.java 25 Jul 2003 18:29:31 -0000 1.20 +++ java/org/apache/commons/httpclient/ConnectMethod.java 11 Aug 2003 02:05:12 -0000 @@ -69,7 +69,7 @@ import org.apache.commons.logging.LogFactory; /** - *
Wraps another method to tunnel through a proxy.
+ * Establishes a tunneled HTTP connection via the CONNECT method. * * @author Ortwin Gl�ck * @author dIon Gillard @@ -79,10 +79,20 @@ * @version $Revision: 1.20 $ $Date: 2003/07/25 18:29:31 $ */ public class ConnectMethod extends HttpMethodBase { + /** the name of this method */ public static final String NAME = "CONNECT"; /** + * Create a connect method. + */ + public ConnectMethod() { + LOG.trace("enter ConnectMethod()"); + } + + /** + * @deprecated the wrapped method is no longer used + * * Create a connect method wrapping the existing method * * @param method the {@link HttpMethod method} to execute after connecting @@ -90,7 +100,6 @@ */ public ConnectMethod(HttpMethod method) { LOG.trace("enter ConnectMethod(HttpMethod)"); - this.method = method; } /** @@ -102,7 +111,6 @@ return NAME; } - /** * This method does nothing. CONNECT request is not supposed * to contain Authorization request header. @@ -168,54 +176,30 @@ } /** - * Execute this method by tunnelling and then executing the wrapped method. + * Execute this method and create a tunneled HttpConnection. If the method + * is successful (i.e. the status is a 2xx) tunnelCreated() will be called + * on the connection. * * @param state the current http state * @param conn the connection to write to * @return the http status code from execution * @throws HttpException when an error occurs writing the headers * @throws IOException when an error occurs writing the headers + * + * @see HttpConnection#tunnelCreated() */ public int execute(HttpState state, HttpConnection conn) throws IOException, HttpException { LOG.trace("enter ConnectMethod.execute(HttpState, HttpConnection)"); int code = super.execute(state, conn); - LOG.debug("CONNECT status code " + code); + if (LOG.isDebugEnabled()) { + LOG.debug("CONNECT status code " + code); + } if ((code >= 200) && (code < 300)) { conn.tunnelCreated(); - code = method.execute(state, conn); - } else { - // What is to follow is an ugly hack. - // I REALLY hate having to resort to such - // an appalling trick - // TODO: Connect method must be redesigned. - // The only feasible solution is to split monolithic - // HttpMethod into HttpRequest/HttpResponse pair. - // That would allow to execute CONNECT method - // behind the scene and return CONNECT HttpResponse - // object in response to the original request that - // contains the correct status line, headers & - // response body. - - LOG.debug("CONNECT failed, fake the response for the original method"); - if (method instanceof HttpMethodBase) { - // Pass the status, headers and response stream to the wrapped - // method. - // To ensure that the connection is not released more than once - // this method is still responsible for releasing the connection. - // This will happen when the response body is consumed, or when - // the wrapped method closes the response connection in - // releaseConnection(). - ((HttpMethodBase) method).fakeResponse( - getStatusLine(), - getResponseHeaderGroup(), - getResponseStream() - ); - } else { - releaseConnection(); - } } + return code; } @@ -283,8 +267,5 @@ /** Log object for this class. */ private static final Log LOG = LogFactory.getLog(ConnectMethod.class); - - /** The wrapped method */ - private HttpMethod method; } 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.79 diff -u -r1.79 HttpClient.java --- java/org/apache/commons/httpclient/HttpClient.java 16 Jul 2003 20:48:27 -0000 1.79 +++ java/org/apache/commons/httpclient/HttpClient.java 11 Aug 2003 02:05:13 -0000 @@ -98,6 +98,7 @@ private static final Log LOG = LogFactory.getLog(HttpClient.class); static { + if (LOG.isDebugEnabled()) { LOG.debug("Java version: " + System.getProperty("java.version")); LOG.debug("Java vendor: " + System.getProperty("java.vendor")); @@ -319,29 +320,32 @@ throw new IllegalArgumentException("HttpMethod parameter may not be null"); } - int soTimeout = 0; - boolean strictMode = false; - int connectionTimeout = 0; - long httpConnectionTimeout = 0; + if (hostConfiguration == null) { + hostConfiguration = ( + method.getHostConfiguration() != null + ? method.getHostConfiguration() + : getHostConfiguration() + ); + } + HostConfiguration defaultHostConfiguration = null; + HttpMethodDirector methodDirector = new HttpMethodDirector(); /* 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; - connectionTimeout = this.connectionTimeout; - httpConnectionTimeout = this.httpConnectionTimeout; - if (state == null) { - state = getState(); - } + methodDirector.setSoTimeout(this.timeoutInMilliseconds); + methodDirector.setStrictMode(this.strictMode); + methodDirector.setConnectionTimeout(this.connectionTimeout); + methodDirector.setHttpConnectionFactoryTimeout(this.httpConnectionTimeout); + methodDirector.setState(state == null ? getState() : state); + methodDirector.setConnectionManager(this.httpConnectionManager); defaultHostConfiguration = getHostConfiguration(); } - HostConfiguration methodConfiguration - = new HostConfiguration(hostConfiguration); + HostConfiguration methodConfiguration = new HostConfiguration(hostConfiguration); if (hostConfiguration != defaultHostConfiguration) { // we may need to apply some defaults @@ -368,41 +372,15 @@ } } - HttpConnectionManager connmanager = this.httpConnectionManager; - - HttpConnection connection = connmanager.getConnectionWithTimeout( - methodConfiguration, - httpConnectionTimeout - ); - - try { - // Catch all possible exceptions to make sure to release the - // connection, as although the user may call - // Method->releaseConnection(), the method doesn't know about the - // connection until HttpMethod.execute() is called. - - method.setStrictMode(strictMode); + methodDirector.setHostConfiguration(methodConfiguration); + methodDirector.setMethod(method); - if (!connection.isOpen()) { - connection.setSoTimeout(soTimeout); - connection.setConnectionTimeout(connectionTimeout); - connection.open(); - if (connection.isProxied() && connection.isSecure()) { - method = new ConnectMethod(method); - } - } - } catch (IOException e) { - connection.releaseConnection(); - throw e; - } catch (RuntimeException e) { - connection.releaseConnection(); - throw e; - } + methodDirector.executeMethod(); - return method.execute(state, connection); + return methodDirector.getMethod().getStatusCode(); } - /** + /** * Return the host that the client is accessing. * * @return The host that the client is accessing, ornull if
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.72
diff -u -r1.72 HttpConnection.java
--- java/org/apache/commons/httpclient/HttpConnection.java 5 Aug 2003 19:29:30 -0000 1.72
+++ java/org/apache/commons/httpclient/HttpConnection.java 11 Aug 2003 02:05:16 -0000
@@ -1017,11 +1017,31 @@
// we are assuming that the connection will only be released once used
used = true;
- if (httpConnectionManager != null) {
+
+ if (locked) {
+ LOG.debug("Connection is locked. Call to releaseConnection() ignored.");
+ } else if (httpConnectionManager != null) {
+ LOG.debug("Releasing connection back to connection manager.");
httpConnectionManager.releaseConnection(this);
+ } else {
+ LOG.warn("HttpConnectionManager is null. Connection cannot be released.");
}
}
+ /**
+ * @return
+ */
+ boolean isLocked() {
+ return locked;
+ }
+
+ /**
+ * @param locked
+ */
+ void setLocked(boolean locked) {
+ this.locked = locked;
+ }
+
// ------------------------------------------------------ Protected Methods
/**
@@ -1314,6 +1334,10 @@
/** TCP_NODELAY socket value */
private boolean soNodelay = true;
+ /** flag to indicate if this connection can be released, if locked the connection cannot be
+ * released */
+ private boolean locked = false;
+
/** Whether or not the socket is a secure one. */
private boolean usingSecureSocket = false;
@@ -1321,9 +1345,9 @@
private boolean tunnelEstablished = false;
/** Whether or not isStale() is used by isOpen() */
- private boolean staleCheckingEnabled = true;
-
- /** Timeout until connection established (Socket created). 0 means no timeout. */
+ private boolean staleCheckingEnabled = true;
+
+ /** Timeout until connection established (Socket created). 0 means no timeout. */
private int connectTimeout = 0;
/** the connection manager that created this connection or null */
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.26
diff -u -r1.26 HttpMethod.java
--- java/org/apache/commons/httpclient/HttpMethod.java 3 Aug 2003 21:59:13 -0000 1.26
+++ java/org/apache/commons/httpclient/HttpMethod.java 11 Aug 2003 02:05:17 -0000
@@ -254,6 +254,14 @@
*/
Header[] getRequestHeaders();
+ /**
+ * Returns the request headers with the given name. Note that header-name matching is
+ * case insensitive.
+ * @param headerName the name of the headers to be returned.
+ * @return an array of zero or more headers
+ */
+ Header[] getRequestHeaders(String headerName);
+
// ---------------------------------------------------------------- Queries
/**
@@ -293,6 +301,14 @@
* @return The specified response header.
*/
Header getResponseHeader(String headerName);
+
+ /**
+ * Returns the response headers with the given name. Note that header-name matching is
+ * case insensitive.
+ * @param headerName the name of the headers to be returned.
+ * @return an array of zero or more headers
+ */
+ Header[] getResponseHeaders(String headerName);
/**
* Returns an array of the response footers that the HTTP method currently has
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.175
diff -u -r1.175 HttpMethodBase.java
--- java/org/apache/commons/httpclient/HttpMethodBase.java 3 Aug 2003 21:59:13 -0000 1.175
+++ java/org/apache/commons/httpclient/HttpMethodBase.java 11 Aug 2003 02:05:22 -0000
@@ -68,14 +68,9 @@
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
-import java.util.HashSet;
-import java.util.Set;
import org.apache.commons.httpclient.auth.AuthScheme;
-import org.apache.commons.httpclient.auth.AuthenticationException;
-import org.apache.commons.httpclient.auth.CredentialsNotAvailableException;
import org.apache.commons.httpclient.auth.HttpAuthenticator;
-import org.apache.commons.httpclient.auth.MalformedChallengeException;
import org.apache.commons.httpclient.cookie.CookiePolicy;
import org.apache.commons.httpclient.cookie.CookieSpec;
import org.apache.commons.httpclient.cookie.MalformedCookieException;
@@ -94,6 +89,17 @@
*
*
* + * When a method's request may contain a body, subclasses will typically want + * to override: + *
* When a method requires additional request headers, subclasses will typically * want to override: *
- * The {@link #isUsed()} is set to true if the write succeeds. - *
- * - * @param state the {@link HttpState state} information associated with this method - * @param conn the {@link HttpConnection connection} used to execute - * this HTTP method - * - * @throws IOException if an I/O (transport) error occurs. Some transport exceptions - * can be recovered from. - * @throws HttpException if a protocol exception occurs. Usually protocol exceptions - * cannot be recovered from. - * - * @see #writeRequest(HttpState,HttpConnection) - * @see #readResponse(HttpState,HttpConnection) - */ - private void processRequest(HttpState state, HttpConnection connection) - throws HttpException, IOException { - LOG.trace("enter HttpMethodBase.processRequest(HttpState, HttpConnection)"); - - int execCount = 0; - boolean requestSent = false; - - // loop until the method is successfully processed, the retryHandler - // returns false or a non-recoverable exception is thrown - while (true) { - execCount++; - requestSent = false; - - if (LOG.isTraceEnabled()) { - LOG.trace("Attempt number " + execCount + " to process request"); - } - try { - if (!connection.isOpen()) { - LOG.debug("Opening the connection."); - connection.open(); - } - writeRequest(state, connection); - requestSent = true; - readResponse(state, connection); - // the method has successfully executed - used = true; - break; - } catch (HttpRecoverableException httpre) { - if (LOG.isDebugEnabled()) { - LOG.debug("Closing the connection."); - } - connection.close(); - LOG.info("Recoverable exception caught when processing request"); - // update the recoverable exception count. - recoverableExceptionCount++; - - // test if this method should be retried - if (!getMethodRetryHandler().retryMethod( - this, - connection, - httpre, - execCount, - requestSent) - ) { - LOG.warn( - "Recoverable exception caught but MethodRetryHandler.retryMethod() " - + "returned false, rethrowing exception" - ); - throw httpre; - } - } - } - } - - /** * Returns the character set from the Content-Type header. + * * @param contentheader The content header. * @return String The character set. */ @@ -2636,6 +2207,8 @@ } /** + * @deprecated no longer used + * * Returns the number of "recoverable" exceptions thrown and handled, to * allow for monitoring the quality of the connection. * @@ -2665,10 +2238,7 @@ responseConnection.close(); } - doneWithConnection = true; - if (!inExecute) { - ensureConnectionRelease(); - } + ensureConnectionRelease(); } /** @@ -2730,7 +2300,7 @@ * * TODO: Remove this crap as soon as possible */ - protected void fakeResponse( + void fakeResponse( StatusLine statusline, HeaderGroup responseheaders, InputStream responseStream Index: java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java,v retrieving revision 1.14 diff -u -r1.14 SimpleHttpConnectionManager.java --- java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 31 Jul 2003 02:47:30 -0000 1.14 +++ java/org/apache/commons/httpclient/SimpleHttpConnectionManager.java 11 Aug 2003 02:05:23 -0000 @@ -132,6 +132,7 @@ if (httpConnection == null) { httpConnection = new HttpConnection(hostConfiguration); + httpConnection.setHttpConnectionManager(this); httpConnection.setStaleCheckingEnabled(connectionStaleCheckingEnabled); } else { @@ -177,7 +178,7 @@ */ public void releaseConnection(HttpConnection conn) { if (conn != httpConnection) { - throw new IllegalStateException("Unexpected close on a different connection."); + throw new IllegalStateException("Unexpected release of an unknown connection."); } finishLastResponse(httpConnection); Index: test/org/apache/commons/httpclient/TestAuthenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestAuthenticator.java,v retrieving revision 1.27 diff -u -r1.27 TestAuthenticator.java --- test/org/apache/commons/httpclient/TestAuthenticator.java 8 Aug 2003 06:47:43 -0000 1.27 +++ test/org/apache/commons/httpclient/TestAuthenticator.java 11 Aug 2003 02:05:27 -0000 @@ -76,9 +76,9 @@ * * @author Rodney Waldhoff * @author Jeff Dever - * @version $Id: TestAuthenticator.java,v 1.27 2003/08/08 06:47:43 olegk Exp $ + * @version $Id: TestAuthenticator.java,v 1.26 2003/07/05 22:31:21 olegk Exp $ */ -public class TestAuthenticator extends TestCase { +public class TestAuthenticator extends TestNoHostBase { // ------------------------------------------------------------ Constructor public TestAuthenticator(String testName) { @@ -484,10 +484,8 @@ public void testNTLMAuthenticationRetry() throws Exception { NTCredentials cred = new NTCredentials("username", "password", "host", "domain"); - HttpState state = new HttpState(); - state.setCredentials(null, null, cred); + client.getState().setCredentials(null, null, cred); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: NTLM\r\n" + @@ -503,7 +501,7 @@ "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n\r\n" + "stuff\r\n"); - method.execute(state, conn); + client.executeMethod(method); assertNull(method.getResponseHeader("WWW-Authenticate")); assertEquals(200, method.getStatusCode()); } @@ -512,12 +510,10 @@ * Test that the Unauthorized response is returned when doAuthentication is false. */ public void testDoAuthenticateFalse() throws Exception { - HttpState state = new HttpState(); - state.setCredentials(null, "Protected", + client.getState().setCredentials(null, "Protected", new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); method.setDoAuthentication(false); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"Protected\"\r\n" + @@ -527,7 +523,7 @@ "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n"); - method.execute(state, conn); + client.executeMethod(method); assertNotNull(method.getResponseHeader("WWW-Authenticate")); assertNull(method.getRequestHeader("Authorization")); assertEquals(401, method.getStatusCode()); @@ -538,18 +534,16 @@ /** */ public void testInvalidCredentials() throws Exception { - HttpState state = new HttpState(); - state.setCredentials(null, "Protected", new UsernamePasswordCredentials("name", "pass")); + client.getState().setCredentials(null, "Protected", new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); method.setDoAuthentication(false); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Basic realm=\"Protected\"\r\n" + "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n" ); - method.execute(state, conn); + client.executeMethod(method); assertEquals(401, method.getStatusCode()); } @@ -557,10 +551,8 @@ // --------------------------------- Test Methods for Multiple Authentication public void testMultipleChallengeBasic() throws Exception { - HttpState state = new HttpState(); - state.setCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); + client.getState().setCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Unsupported\r\n" + @@ -572,8 +564,8 @@ "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n" - ); - method.execute(state, conn); + ); + client.executeMethod(method); Header authHeader = method.getRequestHeader("Authorization"); assertNotNull(authHeader); @@ -582,10 +574,8 @@ } public void testMultipleChallengeBasicLongRealm() throws Exception { - HttpState state = new HttpState(); - state.setCredentials(null, null, new UsernamePasswordCredentials("name", "pass")); + client.getState().setCredentials(null, null, new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Unsupported\r\n" + @@ -598,7 +588,7 @@ "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n" ); - method.execute(state, conn); + client.executeMethod(method); Header authHeader = method.getRequestHeader("Authorization"); assertNotNull(authHeader); @@ -610,10 +600,8 @@ public void testMultipleChallengeDigest() throws Exception { - HttpState state = new HttpState(); - state.setCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); + client.getState().setCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 401 Unauthorized\r\n" + "WWW-Authenticate: Unsupported\r\n" + @@ -627,7 +615,7 @@ "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n" ); - method.execute(state, conn); + client.executeMethod(method); Header authHeader = method.getRequestHeader("Authorization"); assertNotNull(authHeader); @@ -637,10 +625,8 @@ public void testMultipleProxyChallengeBasic() throws Exception { - HttpState state = new HttpState(); - state.setProxyCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); + client.getState().setProxyCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 407 Proxy Authentication Required\r\n" + "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" + @@ -653,7 +639,7 @@ "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n" ); - method.execute(state, conn); + client.executeMethod(method); Header authHeader = method.getRequestHeader("Proxy-Authorization"); assertNotNull(authHeader); @@ -663,10 +649,8 @@ public void testMultipleProxyChallengeDigest() throws Exception { - HttpState state = new HttpState(); - state.setProxyCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); + client.getState().setProxyCredentials("Protected", null, new UsernamePasswordCredentials("name", "pass")); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(); conn.addResponse( "HTTP/1.1 407 Proxy Authentication Required\r\n" + "Proxy-Authenticate: Basic realm=\"Protected\"\r\n" + @@ -680,7 +664,7 @@ "Connection: close\r\n" + "Server: HttpClient Test/2.0\r\n" ); - method.execute(state, conn); + client.executeMethod(method); Header authHeader = method.getRequestHeader("Proxy-Authorization"); assertNotNull(authHeader); 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.9 diff -u -r1.9 TestHttpConnectionManager.java --- test/org/apache/commons/httpclient/TestHttpConnectionManager.java 16 Jul 2003 20:48:28 -0000 1.9 +++ test/org/apache/commons/httpclient/TestHttpConnectionManager.java 11 Aug 2003 02:05:29 -0000 @@ -63,17 +63,22 @@ 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.protocol.Protocol; +import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; /** * Unit tests for {@link HttpConnectionManager}. * * @author Marc A. Saegesser - * @version $Id: TestHttpConnectionManager.java,v 1.9 2003/07/16 20:48:28 olegk Exp $ + * @version $Id: TestHttpConnectionManager.java,v 1.8 2003/04/28 23:19:58 mbecke Exp $ */ public class TestHttpConnectionManager extends TestLocalHostBase { @@ -116,19 +121,24 @@ MultiThreadedHttpConnectionManager mgr = new MultiThreadedHttpConnectionManager(); mgr.setMaxTotalConnections(1); + HttpClient client = createHttpClient(mgr); // we're going to execute a connect method against the localhost, assuming // that CONNECT is not supported. This should test the fakeResponse() // code on HttpMethodBase. - HostConfiguration hostConfiguration = new HostConfiguration(); - hostConfiguration.setHost(getHost(), getPort(), getProtocol()); + client.getHostConfiguration().setProxy(getHost(), getPort()); + // we must set the host to a secure destination or the CONNECT method + // will not be used + client.getHostConfiguration().setHost( + "notARealHost", + 1234, + new Protocol("https", new FakeSecureProtocolSocketFactory(), 443) + ); GetMethod get = new GetMethod("/"); try { - HttpConnection connection = mgr.getConnection(hostConfiguration); - ConnectMethod connect = new ConnectMethod(get); - assertTrue(connect.execute(new HttpState(), connection) != 200); - } catch (Exception e) { + assertTrue(client.executeMethod(get) != 200); + } catch (IOException e) { e.printStackTrace(); fail("Error executing connect: " + e); } @@ -136,18 +146,16 @@ // this should calling releaseConnection() releases the connection try { get.releaseConnection(); - mgr.getConnectionWithTimeout(hostConfiguration, 1).releaseConnection(); - } catch (Exception e1) { + mgr.getConnectionWithTimeout(client.getHostConfiguration(), 1).releaseConnection(); + } catch (ConnectTimeoutException e1) { fail("Connection should have been available."); } get = new GetMethod("/"); try { - HttpConnection connection = mgr.getConnection(hostConfiguration); - ConnectMethod connect = new ConnectMethod(get); - assertTrue(connect.execute(new HttpState(), connection) != 200); - } catch (Exception e) { + assertTrue(client.executeMethod(get) != 200); + } catch (IOException e) { e.printStackTrace(); fail("Error executing connect: " + e); } @@ -155,7 +163,7 @@ // make sure reading the response fully releases the connection try { get.getResponseBodyAsString(); - mgr.getConnectionWithTimeout(hostConfiguration, 1).releaseConnection(); + mgr.getConnectionWithTimeout(client.getHostConfiguration(), 1).releaseConnection(); } catch (ConnectTimeoutException e1) { fail("Connection should have been available."); } @@ -163,10 +171,8 @@ get = new GetMethod("/"); try { - HttpConnection connection = mgr.getConnection(hostConfiguration); - ConnectMethod connect = new ConnectMethod(get); - assertTrue(connect.execute(new HttpState(), connection) != 200); - } catch (Exception e) { + assertTrue(client.executeMethod(get) != 200); + } catch (IOException e) { e.printStackTrace(); fail("Error executing connect: " + e); } @@ -174,7 +180,7 @@ // make sure closing the output stream releases the connection try { get.getResponseBodyAsStream().close(); - mgr.getConnectionWithTimeout(hostConfiguration, 1).releaseConnection(); + mgr.getConnectionWithTimeout(client.getHostConfiguration(), 1).releaseConnection(); } catch (ConnectTimeoutException e) { fail("Connection should have been available."); } catch (IOException e) { @@ -224,7 +230,7 @@ HttpClient client = createHttpClient(connectionManager); // we shouldn't have to wait if a connection is available - client.setHttpConnectionFactoryTimeout( 1 ); + client.setHttpConnectionFactoryTimeout(1); GetMethod getMethod = new GetMethod("/"); @@ -242,6 +248,7 @@ } catch (HttpException e) { fail("error reading from server; " + e); } catch (IOException e) { + e.printStackTrace(); fail("error reading from server; " + e); } @@ -528,6 +535,25 @@ }catch(ConnectTimeoutException e){ //Expected result } + } + + static class FakeSecureProtocolSocketFactory implements SecureProtocolSocketFactory { + + public Socket createSocket(Socket socket, String host, int port, boolean autoClose) + throws IOException, UnknownHostException { + throw new IllegalStateException("createSocket() should never have been called."); + } + + public Socket createSocket(String host, int port) + throws IOException, UnknownHostException { + throw new IllegalStateException("createSocket() should never have been called."); + } + + public Socket createSocket(String host, int port, InetAddress clientHost, int clientPort) + throws IOException, UnknownHostException { + throw new IllegalStateException("createSocket() should never have been called."); + } + } static class GetConnectionThread extends Thread { Index: test/org/apache/commons/httpclient/TestMethodsNoHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMethodsNoHost.java,v retrieving revision 1.19 diff -u -r1.19 TestMethodsNoHost.java --- test/org/apache/commons/httpclient/TestMethodsNoHost.java 19 Jun 2003 20:52:07 -0000 1.19 +++ test/org/apache/commons/httpclient/TestMethodsNoHost.java 11 Aug 2003 02:05:31 -0000 @@ -67,11 +67,11 @@ import java.io.Reader; import junit.framework.Test; -import junit.framework.TestCase; import junit.framework.TestSuite; + import org.apache.commons.httpclient.methods.GetMethod; -import org.apache.commons.httpclient.methods.PostMethod; import org.apache.commons.httpclient.methods.HeadMethod; +import org.apache.commons.httpclient.methods.PostMethod; /** * @author Rodney Waldhoff @@ -80,7 +80,7 @@ * @author Oleg Kalnichevski * @version $Revision: 1.19 $ $Date: 2003/06/19 20:52:07 $ */ -public class TestMethodsNoHost extends TestCase { +public class TestMethodsNoHost extends TestNoHostBase { static final String NAME = "name", VALUE = "value"; static final String NAME0 = "name0", VALUE0 = "value0"; @@ -186,7 +186,6 @@ } public void testConnectionAutoClose() throws Exception { - SimpleHttpConnection conn = new SimpleHttpConnection(); String headers = "HTTP/1.1 200 OK\r\n" +"Date: Wed, 28 Mar 2001 05:05:04 GMT\r\n" +"Connection: close\r\n"; @@ -199,7 +198,7 @@ conn.addResponse(headers, body); conn.open(); HttpMethodBase method = new GetMethod("/"); - method.execute(new HttpState(), conn); + client.executeMethod(method); Reader response = new InputStreamReader(method.getResponseBodyAsStream()); int c; while ((c = response.read()) != -1) { @@ -214,7 +213,7 @@ conn.addResponse(headers, ""); try { - headMethod.execute(new HttpState(), conn); + client.executeMethod(headMethod); conn.assertNotOpen(); } catch (Throwable t) { @@ -250,7 +249,6 @@ * Make sure that its OK to call releaseConnection if the connection has not been. */ public void testReleaseConnection() { - HttpClient client = new HttpClient(); HttpMethod method = new GetMethod("http://bogus.url/path/"); method.releaseConnection(); } Index: test/org/apache/commons/httpclient/TestMethodsRedirectNoHost.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMethodsRedirectNoHost.java,v retrieving revision 1.6 diff -u -r1.6 TestMethodsRedirectNoHost.java --- test/org/apache/commons/httpclient/TestMethodsRedirectNoHost.java 20 Apr 2003 23:26:23 -0000 1.6 +++ test/org/apache/commons/httpclient/TestMethodsRedirectNoHost.java 11 Aug 2003 02:05:31 -0000 @@ -63,21 +63,17 @@ package org.apache.commons.httpclient; import junit.framework.Test; -import junit.framework.TestCase; import junit.framework.TestSuite; -import org.apache.commons.httpclient.methods.*; +import org.apache.commons.httpclient.methods.PostMethod; /** * @author Jeff Dever * @version $Revision: 1.6 $ */ -public class TestMethodsRedirectNoHost extends TestCase { +public class TestMethodsRedirectNoHost extends TestNoHostBase { - SimpleHttpConnection conn; - - // ------------------------------------------------------------ Constructor public TestMethodsRedirectNoHost(String testName) { @@ -90,11 +86,6 @@ return new TestSuite(TestMethodsRedirectNoHost.class); } - public void setUp() throws Exception{ - conn = new SimpleHttpConnection(); - } - - private void addRedirectResponse(String location) { String headers = "HTTP/1.1 302 Redirect\r\n" +"Date: Wed, 28 Mar 2002 05:05:04 GMT\r\n" @@ -118,9 +109,9 @@ addOkResponse(); conn.open(); - HttpMethod method = new SimpleHttpMethod("/oldfile"); + HttpMethod method = new SimpleHttpMethod("http://localhost/oldfile"); method.setFollowRedirects(true); - method.execute(new HttpState(), conn); + client.executeMethod(method); Header locationHeader = method.getResponseHeader("Location"); assertEquals(200, method.getStatusCode()); assertEquals("/newfile", method.getPath()); @@ -135,7 +126,7 @@ HttpMethod method = new SimpleHttpMethod("/oldfile"); method.setFollowRedirects(true); - method.execute(new HttpState(), conn); + client.executeMethod(method); Header locationHeader = method.getResponseHeader("Location"); assertEquals(200, method.getStatusCode()); assertEquals("/newfile", method.getPath()); @@ -150,7 +141,7 @@ PostMethod method = new PostMethod("/oldfile"); method.setRequestBody(new NameValuePair[] { new NameValuePair("name", "value") } ); - method.execute(new HttpState(), conn); + client.executeMethod(method); Header locationHeader = method.getResponseHeader("Location"); assertEquals(302, method.getStatusCode()); assertEquals("/oldfile", method.getPath()); @@ -182,7 +173,7 @@ HttpMethod method = new SimpleHttpMethod("/oldfile"); method.setFollowRedirects(true); method.setStrictMode(false); - method.execute(new HttpState(), conn); + client.executeMethod(method); Header locationHeader = method.getResponseHeader("Location"); assertEquals(200, method.getStatusCode()); assertEquals("/newfile", method.getPath()); @@ -266,13 +257,11 @@ addOkResponse(); conn.open(); - HttpState state = new HttpState(); - state.addCookie( - new Cookie("localhost", "name", "value", "/", -1, false)); + client.getState().addCookie(new Cookie("localhost", "name", "value", "/", -1, false)); HttpMethod method = new SimpleHttpMethod("/oldfile"); method.setFollowRedirects(true); - method.execute(state, conn); + client.executeMethod(method); Header locationHeader = method.getResponseHeader("Location"); assertEquals(200, method.getStatusCode()); Index: test/org/apache/commons/httpclient/TestResponseHeaders.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestResponseHeaders.java,v retrieving revision 1.8 diff -u -r1.8 TestResponseHeaders.java --- test/org/apache/commons/httpclient/TestResponseHeaders.java 20 Jun 2003 13:33:09 -0000 1.8 +++ test/org/apache/commons/httpclient/TestResponseHeaders.java 11 Aug 2003 02:05:32 -0000 @@ -76,7 +76,7 @@ * @author Adrian Sutton * @version $Id: TestResponseHeaders.java,v 1.8 2003/06/20 13:33:09 adrian Exp $ */ -public class TestResponseHeaders extends TestCase { +public class TestResponseHeaders extends TestNoHostBase { // ------------------------------------------------------------ Constructor public TestResponseHeaders(String testName) { @@ -94,20 +94,6 @@ return new TestSuite(TestResponseHeaders.class); } - - - /** - * Simple extension of HttpMethodBase. - */ - private class SimpleHttpMethod extends HttpMethodBase { - public SimpleHttpMethod() { - super(""); - } - public String getName() { - return "simple"; - } - } - // ----------------------------------------------------------- Test Methods public void testHeaders() throws Exception { String body = "XXX\r\nYYY\r\nZZZ"; @@ -118,10 +104,9 @@ "Content-Type: text/xml; charset=utf-8\r\n" + "Date: Wed, 28 Mar 2001 05:05:04 GMT\r\n" + "Server: UserLand Frontier/7.0-WinNT\r\n"; - HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(headers, body); - method.execute(state, conn); + conn.addResponse(headers, body); + client.executeMethod(method); assertEquals("close", method.getResponseHeader("Connection").getValue()); assertEquals(body.length(), Integer.parseInt(method.getResponseHeader("Content-Length").getValue())); assertEquals("text/xml; charset=utf-8", method.getResponseHeader("Content-Type").getValue()); @@ -139,17 +124,15 @@ "HTTP/1.1 200 OK\r\n" + "Content-Length: " + body.length() + "\r\n" + "Content-Length: " + body.length() + "\r\n"; - HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(headers, body); - method.execute(state, conn); + conn.addResponse(headers, body); + client.executeMethod(method); assertNotNull( "Response body is null.", method.getResponseBodyAsStream() ); } public void testDuplicateProxyConnection() throws Exception { - SimpleHttpConnection conn = new SimpleHttpConnection(); String headers = "HTTP/1.1 200 OK\r\n" + "proxy-connection: close\r\n" @@ -161,12 +144,11 @@ conn.setProxyHost("proxy"); conn.setProxyPort(1); GetMethod method = new GetMethod("/"); - method.execute(new HttpState(), conn); + client.executeMethod(method); method.getResponseBodyAsString(); assertFalse(conn.isOpen()); - conn = new SimpleHttpConnection(); headers = "HTTP/1.0 200 OK\r\n" + "proxy-connection: keep-alive\r\n" @@ -178,7 +160,7 @@ conn.setProxyHost("proxy"); conn.setProxyPort(1); method = new GetMethod("/"); - method.execute(new HttpState(), conn); + client.executeMethod(method); method.getResponseBodyAsString(); assertTrue(conn.isOpen()); @@ -186,21 +168,19 @@ public void testDuplicateConnection() throws Exception { - SimpleHttpConnection conn = new SimpleHttpConnection(); String headers = "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Connection: close\r\n" + "\r\n"; - conn.addResponse(headers, ""); GetMethod method = new GetMethod("/"); - method.execute(new HttpState(), conn); + conn.addResponse(headers, ""); + client.executeMethod(method); method.getResponseBodyAsString(); assertFalse(conn.isOpen()); - conn = new SimpleHttpConnection(); headers = "HTTP/1.0 200 OK\r\n" +"Connection: keep-alive\r\n" @@ -208,9 +188,9 @@ + "Content-Length: 0\r\n" +"\r\n"; - conn.addResponse(headers, ""); method = new GetMethod("/"); - method.execute(new HttpState(), conn); + conn.addResponse(headers, ""); + client.executeMethod(method); method.getResponseBodyAsString(); assertTrue(conn.isOpen()); @@ -218,27 +198,25 @@ public void testNoContentLength() throws Exception { // test with connection header - SimpleHttpConnection conn = new SimpleHttpConnection(); String headers = "HTTP/1.1 200 OK\r\n" + "Connection: keep-alive\r\n" + "\r\n"; - conn.addResponse(headers, "12345"); GetMethod method = new GetMethod("/"); - method.execute(new HttpState(), conn); + conn.addResponse(headers, "12345"); + client.executeMethod(method); method.getResponseBodyAsString(); assertFalse(conn.isOpen()); // test without connection header - conn = new SimpleHttpConnection(); headers = "HTTP/1.1 200 OK\r\n\r\n"; // test with connection header - conn.addResponse(headers, "12345"); method = new GetMethod("/"); - method.execute(new HttpState(), conn); + conn.addResponse(headers, "12345"); + client.executeMethod(method); method.getResponseBodyAsString(); assertFalse(conn.isOpen()); @@ -246,30 +224,28 @@ public void testProxyNoContentLength() throws Exception { // test with proxy-connection header - SimpleHttpConnection conn = new SimpleHttpConnection(); String headers = "HTTP/1.1 200 OK\r\n" + "proxy-connection: keep-alive\r\n" + "\r\n"; - conn.addResponse(headers, "12345"); conn.setProxyHost("proxy"); conn.setProxyPort(1); GetMethod method = new GetMethod("/"); - method.execute(new HttpState(), conn); + conn.addResponse(headers, "12345"); + client.executeMethod(method); method.getResponseBodyAsString(); assertFalse(conn.isOpen()); // test without proxy-connection header - conn = new SimpleHttpConnection(); headers = "HTTP/1.1 200 OK\r\n\r\n"; - conn.addResponse(headers, "12345"); conn.setProxyHost("proxy"); conn.setProxyPort(1); method = new GetMethod("/"); - method.execute(new HttpState(), conn); + conn.addResponse(headers, "12345"); + client.executeMethod(method); method.getResponseBodyAsString(); assertFalse(conn.isOpen()); @@ -280,10 +256,9 @@ String headers = "HTTP/1.1 200 OK\r\n" + "Content-Length: " + body.length() + "\r\n"; - HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(headers, body); - method.execute(state, conn); + conn.addResponse(headers, body); + client.executeMethod(method); assertEquals(null, method.getResponseHeader(null)); assertEquals(null, method.getResponseHeader("bogus")); } @@ -299,10 +274,9 @@ "Date: Wed, 28 Mar 2001\r\n" + " 05:05:04 GMT\r\n" + "Server: UserLand Frontier/7.0-WinNT\r\n"; - HttpState state = new HttpState(); HttpMethod method = new SimpleHttpMethod(); - SimpleHttpConnection conn = new SimpleHttpConnection(headers, body); - method.execute(state, conn); + conn.addResponse(headers, body); + client.executeMethod(method); assertEquals("close", method.getResponseHeader("Connection").getValue()); assertEquals(body.length(), Integer.parseInt(method.getResponseHeader("Content-Length").getValue())); assertEquals("text/xml; charset=utf-8 boundary=XXXX", method.getResponseHeader("Content-Type").getValue()); Index: src/java/org/apache/commons/httpclient/HttpMethodDirector.java =================================================================== RCS file: src/java/org/apache/commons/httpclient/HttpMethodDirector.java diff -N src/java/org/apache/commons/httpclient/HttpMethodDirector.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/commons/httpclient/HttpMethodDirector.java 1 Jan 1970 00:00:00 -0000 @@ -0,0 +1,758 @@ +/* + * $Header: $ + * $Revision: $ + * $Date: $ + * + * ==================================================================== + * + * The Apache Software License, Version 1.1 + * + * Copyright (c) 1999-2003 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", "Commons", 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 + *authenticationPreemtive has been set.
+ *
+ * @see HttpState#isAuthenticationPreemptive()
+ */
+ private void addPreemtiveAuthenticationHeaders() {
+
+ //pre-emptively add the authorization header, if required.
+ if (state.isAuthenticationPreemptive()) {
+
+ LOG.debug("Preemptively sending default basic credentials");
+
+ try {
+ if (HttpAuthenticator.authenticateDefault(method, connection, state)) {
+ LOG.debug("Default basic credentials applied");
+ }
+ if (connection.isProxied()) {
+ if (HttpAuthenticator.authenticateProxyDefault(method, connection, state)) {
+ LOG.debug("Default basic proxy credentials applied");
+ }
+ }
+ } catch (AuthenticationException e) {
+ // Log error and move on
+ LOG.error(e.getMessage(), e);
+ }
+ }
+ }
+
+ /**
+ * Makes sure there is a connection allocated and that it is valid and open.
+ *
+ * @return true if a valid connection was established,
+ * false otherwise
+ *
+ * @throws IOException
+ * @throws HttpException
+ */
+ private boolean establishValidOpenConnection() throws IOException, HttpException {
+
+ // make sure the connection we have is appropriate
+ if (connection != null && !hostConfiguration.hostEquals(connection)) {
+ connection.setLocked(false);
+ connection.releaseConnection();
+ connection = null;
+ }
+
+ // get a connection, if we need one
+ if (connection == null) {
+ connection = connectionManager.getConnectionWithTimeout(
+ hostConfiguration,
+ httpConnectionFactoryTimeout
+ );
+ connection.setLocked(true);
+
+ realms = new HashSet();
+ proxyRealms = new HashSet();
+
+ addPreemtiveAuthenticationHeaders();
+ }
+
+ try {
+ // Catch all possible exceptions to make sure to release the
+ // connection, as although the user may call
+ // Method->releaseConnection(), the method doesn't know about the
+ // connection until HttpMethod.execute() is called.
+
+ if (!connection.isOpen()) {
+ // this connection must be opened before it can be used
+ connection.setSoTimeout(soTimeout);
+ connection.setConnectionTimeout(connectionTimeout);
+ connection.open();
+ if (connection.isProxied() && connection.isSecure()) {
+ // we need to create a secure tunnel before we can execute the real method
+ if (!executeConnect()) {
+ // abort, the connect method failed
+ return false;
+ }
+ }
+ } else if (
+ !(method instanceof ConnectMethod)
+ && connection.isProxied()
+ && connection.isSecure()
+ && !connection.isTransparent()
+ ) {
+ // this connection is open but the secure tunnel has not be created yet,
+ // execute the connect again
+ if (!executeConnect()) {
+ // abort, the connect method failed
+ return false;
+ }
+ }
+
+ } catch (IOException e) {
+ releaseConnection = true;
+ throw e;
+ } catch (RuntimeException e) {
+ releaseConnection = true;
+ throw e;
+ }
+
+ return true;
+ }
+
+ /**
+ * Executes a method with the current hostConfiguration.
+ *
+ * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
+ * can be recovered from.
+ * @throws HttpException if a protocol exception occurs. Usually protocol exceptions
+ * cannot be recovered from.
+ */
+ private void executeMethodForHost() throws IOException, HttpException {
+
+ int execCount = 0;
+ // TODO: how do we get requestSent?
+ boolean requestSent = false;
+
+ // loop until the method is successfully processed, the retryHandler
+ // returns false or a non-recoverable exception is thrown
+ while (true) {
+ execCount++;
+ requestSent = false;
+
+ if (!establishValidOpenConnection()) {
+ return;
+ }
+
+ if (LOG.isTraceEnabled()) {
+ LOG.trace("Attempt number " + execCount + " to process request");
+ }
+ try {
+ method.execute(state, connection);
+ break;
+ } catch (HttpRecoverableException httpre) {
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Closing the connection.");
+ }
+ connection.close();
+ LOG.info("Recoverable exception caught when processing request");
+ // update the recoverable exception count.
+ recoverableExceptionCount++;
+
+ // test if this method should be retried
+ if (!getMethodRetryHandler().retryMethod(
+ method,
+ connection,
+ httpre,
+ execCount,
+ requestSent)
+ ) {
+ LOG.warn(
+ "Recoverable exception caught but MethodRetryHandler.retryMethod() "
+ + "returned false, rethrowing exception"
+ );
+ throw httpre;
+ }
+ }
+ }
+
+ }
+
+ private MethodRetryHandler getMethodRetryHandler() {
+
+ if (method instanceof HttpMethodBase) {
+ return ((HttpMethodBase) method).getMethodRetryHandler();
+ } else {
+ return new DefaultMethodRetryHandler();
+ }
+ }
+
+ /**
+ * Executes a ConnectMethod to establish a tunneled connection.
+ *
+ * @return true if the connect was successful
+ *
+ * @throws IOException
+ * @throws HttpException
+ */
+ private boolean executeConnect() throws IOException, HttpException {
+
+ ConnectMethod connectMethod = new ConnectMethod();
+
+ HttpMethod tempMethod = this.method;
+ this.method = connectMethod;
+
+ try {
+ executeMethod();
+ } catch (HttpException e) {
+ this.method = tempMethod;
+ throw e;
+ } catch (IOException e) {
+ this.method = tempMethod;
+ throw e;
+ }
+
+ int code = method.getStatusCode();
+
+ if ((code >= 200) && (code < 300)) {
+ this.method = tempMethod;
+ return true;
+ } else {
+ // What is to follow is an ugly hack.
+ // I REALLY hate having to resort to such
+ // an appalling trick
+ // TODO: Connect method must be redesigned.
+ // The only feasible solution is to split monolithic
+ // HttpMethod into HttpRequest/HttpResponse pair.
+ // That would allow to execute CONNECT method
+ // behind the scene and return CONNECT HttpResponse
+ // object in response to the original request that
+ // contains the correct status line, headers &
+ // response body.
+
+ LOG.debug("CONNECT failed, fake the response for the original method");
+ // Pass the status, headers and response stream to the wrapped
+ // method.
+ // To ensure that the connection is not released more than once
+ // this method is still responsible for releasing the connection.
+ // This will happen when the response body is consumed, or when
+ // the wrapped method closes the response connection in
+ // releaseConnection().
+ if (tempMethod instanceof HttpMethodBase) {
+ ((HttpMethodBase) tempMethod).fakeResponse(
+ connectMethod.getStatusLine(),
+ connectMethod.getResponseHeaderGroup(),
+ connectMethod.getResponseBodyAsStream()
+ );
+ } else {
+ releaseConnection = true;
+ LOG.warn(
+ "Unable to fake response on method as it is not derived from HttpMethodBase.");
+ }
+ this.method = tempMethod;
+ return false;
+ }
+ }
+
+ /**
+ * Process the redirect response.
+ *
+ * @return true if the redirect was successful
+ */
+ private boolean processRedirectResponse() {
+
+ if (!method.getFollowRedirects()) {
+ LOG.info("Redirect requested but followRedirects is "
+ + "disabled");
+ return false;
+ }
+
+ //get the location header to find out where to redirect to
+ Header locationHeader = method.getResponseHeader("location");
+ if (locationHeader == null) {
+ // got a redirect response, but no location header
+ LOG.error("Received redirect response " + method.getStatusCode()
+ + " but no location header");
+ return false;
+ }
+ String location = locationHeader.getValue();
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Redirect requested to location '" + location
+ + "'");
+ }
+
+ //rfc2616 demands the location value be a complete URI
+ //Location = "Location" ":" absoluteURI
+ URI redirectUri = null;
+ URI currentUri = null;
+
+ try {
+ currentUri = new URI(
+ connection.getProtocol().getScheme(),
+ null,
+ connection.getHost(),
+ connection.getPort(),
+ method.getPath()
+ );
+ redirectUri = new URI(location, true);
+ if (redirectUri.isRelativeURI()) {
+ if (method.isStrictMode()) {
+ LOG.warn("Redirected location '" + location
+ + "' is not acceptable in strict mode");
+ return false;
+ } else {
+ //location is incomplete, use current values for defaults
+ LOG.debug("Redirect URI is not absolute - parsing as relative");
+ redirectUri = new URI(currentUri, redirectUri);
+ }
+ }
+ } catch (URIException e) {
+ LOG.warn("Redirected location '" + location + "' is malformed");
+ return false;
+ }
+
+ //invalidate the list of authentication attempts
+ this.realms.clear();
+ //remove exisitng authentication headers
+ method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP);
+ //update the current location with the redirect location.
+ //avoiding use of URL.getPath() and URL.getQuery() to keep
+ //jdk1.2 comliance.
+ method.setPath(redirectUri.getEscapedPath());
+ method.setQueryString(redirectUri.getEscapedQuery());
+ hostConfiguration.setHost(redirectUri);
+
+ if (LOG.isDebugEnabled()) {
+ LOG.debug("Redirecting from '" + currentUri.getEscapedURI()
+ + "' to '" + redirectUri.getEscapedURI());
+ }
+
+ return true;
+ }
+
+ /**
+ * Processes a response that requires authentication
+ *
+ * @param state the current state
+ * @param conn The connection
+ *
+ * @return true if the request has completed processing, false
+ * if more attempts are needed
+ */
+ private boolean processAuthenticationResponse(HttpState state, HttpConnection conn) {
+ LOG.trace("enter HttpMethodBase.processAuthenticationResponse("
+ + "HttpState, HttpConnection)");
+
+ int statusCode = method.getStatusCode();
+ // handle authentication required
+ Header[] challenges = null;
+ Set realmsUsed = null;
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ challenges = method.getResponseHeaders(HttpAuthenticator.WWW_AUTH);
+ realmsUsed = realms;
+ break;
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ challenges = method.getResponseHeaders(HttpAuthenticator.PROXY_AUTH);
+ realmsUsed = proxyRealms;
+ break;
+ }
+ boolean authenticated = false;
+ // if there was a header requesting authentication
+ if (challenges.length > 0) {
+ AuthScheme authscheme = null;
+ try {
+ authscheme = HttpAuthenticator.selectAuthScheme(challenges);
+ } catch (MalformedChallengeException e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error(e.getMessage(), e);
+ }
+ return true;
+ } catch (UnsupportedOperationException e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error(e.getMessage(), e);
+ }
+ return true;
+ }
+
+ StringBuffer buffer = new StringBuffer();
+ buffer.append(conn.getHost());
+ int port = conn.getPort();
+ if (conn.getProtocol().getDefaultPort() != port) {
+ buffer.append(':');
+ buffer.append(port);
+ }
+ buffer.append('#');
+ buffer.append(authscheme.getID());
+ String realm = buffer.toString();
+
+ if (realmsUsed.contains(realm)) {
+ if (LOG.isInfoEnabled()) {
+ LOG.info("Already tried to authenticate to \""
+ + realm + "\" but still receiving "
+ + statusCode + ".");
+ }
+ return true;
+ } else {
+ realmsUsed.add(realm);
+ }
+
+ try {
+ //remove preemptive header and reauthenticate
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ method.removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP);
+ authenticated = HttpAuthenticator.authenticate(
+ authscheme, method, conn, state);
+ this.realm = authscheme.getRealm();
+ break;
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ method.removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP);
+ authenticated = HttpAuthenticator.authenticateProxy(
+ authscheme, method, conn, state);
+ this.proxyRealm = authscheme.getRealm();
+ break;
+ }
+ } catch (CredentialsNotAvailableException e) {
+ if (LOG.isWarnEnabled()) {
+ LOG.warn(e.getMessage());
+ }
+ return true; // finished request
+ } catch (AuthenticationException e) {
+ if (LOG.isErrorEnabled()) {
+ LOG.error(e.getMessage(), e);
+ }
+ return true; // finished request
+ }
+ if (!authenticated) {
+ // won't be able to authenticate to this challenge
+ // without additional information
+ LOG.debug("HttpMethodBase.execute(): Server demands "
+ + "authentication credentials, but none are "
+ + "available, so aborting.");
+ } else {
+ LOG.debug("HttpMethodBase.execute(): Server demanded "
+ + "authentication credentials, will try again.");
+ // let's try it again, using the credentials
+ }
+ }
+
+ return !authenticated; // finished processing if we aren't authenticated
+ }
+
+ /**
+ * Returns true if a retry is needed.
+ *
+ * @return boolean true if a retry is needed.
+ */
+ private boolean isRetryNeeded() {
+ switch (method.getStatusCode()) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ LOG.debug("Authorization required");
+ if (method.getDoAuthentication()) { //process authentication response
+ //if the authentication is successful, return the statusCode
+ //otherwise, drop through the switch and try again.
+ if (processAuthenticationResponse(state, connection)) {
+ return false;
+ }
+ } else { //let the client handle the authenticaiton
+ return false;
+ }
+ break;
+
+ case HttpStatus.SC_MOVED_TEMPORARILY:
+ case HttpStatus.SC_MOVED_PERMANENTLY:
+ case HttpStatus.SC_SEE_OTHER:
+ case HttpStatus.SC_TEMPORARY_REDIRECT:
+ LOG.debug("Redirect required");
+
+ if (!processRedirectResponse()) {
+ return false;
+ }
+ break;
+
+ default:
+ // neither an unauthorized nor a redirect response
+ return false;
+ } //end of switch
+
+ return true;
+ }
+
+ /**
+ * @return
+ */
+ public HostConfiguration getHostConfiguration() {
+ return hostConfiguration;
+ }
+
+ /**
+ * @param hostConfiguration
+ */
+ public void setHostConfiguration(HostConfiguration hostConfiguration) {
+ this.hostConfiguration = hostConfiguration;
+ }
+
+ /**
+ * @return
+ */
+ public HttpMethod getMethod() {
+ return method;
+ }
+
+ /**
+ * @param method
+ */
+ public void setMethod(HttpMethod method) {
+ this.method = method;
+ }
+
+ /**
+ * @return
+ */
+ public HttpState getState() {
+ return state;
+ }
+
+ /**
+ * @param state
+ */
+ public void setState(HttpState state) {
+ this.state = state;
+ }
+
+ /**
+ * @return
+ */
+ public HttpConnectionManager getConnectionManager() {
+ return connectionManager;
+ }
+
+ /**
+ * @param connectionManager
+ */
+ public void setConnectionManager(HttpConnectionManager connectionManager) {
+ this.connectionManager = connectionManager;
+ }
+
+ /**
+ * @return
+ */
+ public int getConnectionTimeout() {
+ return connectionTimeout;
+ }
+
+ /**
+ * @param connectionTimeout
+ */
+ public void setConnectionTimeout(int connectionTimeout) {
+ this.connectionTimeout = connectionTimeout;
+ }
+
+ /**
+ * @return
+ */
+ public long getHttpConnectionFactoryTimeout() {
+ return httpConnectionFactoryTimeout;
+ }
+
+ /**
+ * @param httpConnectionFactoryTimeout
+ */
+ public void setHttpConnectionFactoryTimeout(long httpConnectionTimeout) {
+ this.httpConnectionFactoryTimeout = httpConnectionTimeout;
+ }
+
+ /**
+ * @return
+ */
+ public int getSoTimeout() {
+ return soTimeout;
+ }
+
+ /**
+ * @param soTimeout
+ */
+ public void setSoTimeout(int soTimeout) {
+ this.soTimeout = soTimeout;
+ }
+
+ /**
+ * @return
+ */
+ public boolean isStrictMode() {
+ return strictMode;
+ }
+
+ /**
+ * @param strictMode
+ */
+ public void setStrictMode(boolean strictMode) {
+ this.strictMode = strictMode;
+ }
+
+}
Index: src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java
===================================================================
RCS file: src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java
diff -N src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/test/org/apache/commons/httpclient/NoHostHttpConnectionManager.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,77 @@
+/*
+ * Created on Jul 21, 2003
+ *
+ * To change the template for this generated file go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+package org.apache.commons.httpclient;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * @author mbecke
+ *
+ * To change the template for this generated type comment go to
+ * Window>Preferences>Java>Code Generation>Code and Comments
+ */
+public class NoHostHttpConnectionManager implements HttpConnectionManager {
+
+ private HttpConnection connection;
+
+ public NoHostHttpConnectionManager() {
+ this.connection = new SimpleHttpConnection();
+ }
+
+ /**
+ * @param connection
+ */
+ public void setConnection(HttpConnection connection) {
+ this.connection = connection;
+ }
+
+ public HttpConnection getConnection(HostConfiguration hostConfiguration) {
+ return connection;
+ }
+
+ public HttpConnection getConnection(HostConfiguration hostConfiguration, long timeout)
+ throws HttpException {
+ return connection;
+ }
+
+ public HttpConnection getConnectionWithTimeout(
+ HostConfiguration hostConfiguration,
+ long timeout)
+ throws ConnectTimeoutException {
+ return connection;
+ }
+
+ public void releaseConnection(HttpConnection conn) {
+ if (conn != connection) {
+ throw new IllegalStateException("Unexpected close on a different connection.");
+ }
+
+ finishLastResponse(connection);
+ }
+
+ /**
+ * Since the same connection is about to be reused, make sure the
+ * previous request was completely processed, and if not
+ * consume it now.
+ * @param conn The connection
+ */
+ static void finishLastResponse(HttpConnection conn) {
+ InputStream lastResponse = conn.getLastResponseInputStream();
+ if (lastResponse != null) {
+ conn.setLastResponseInputStream(null);
+ try {
+ lastResponse.close();
+ } catch (IOException ioe) {
+ //FIXME: badness - close to force reconnect.
+ conn.close();
+ }
+ }
+ }
+
+
+}
Index: src/test/org/apache/commons/httpclient/TestNoHostBase.java
===================================================================
RCS file: src/test/org/apache/commons/httpclient/TestNoHostBase.java
diff -N src/test/org/apache/commons/httpclient/TestNoHostBase.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/test/org/apache/commons/httpclient/TestNoHostBase.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,37 @@
+package org.apache.commons.httpclient;
+
+import junit.framework.TestCase;
+
+/**
+ */
+public abstract class TestNoHostBase extends TestCase {
+
+ protected SimpleHttpConnection conn;
+
+ protected HttpClient client;
+
+ protected NoHostHttpConnectionManager connectionManager;
+
+ /**
+ *
+ */
+ public TestNoHostBase() {
+ super();
+ }
+
+ /**
+ * @param arg0
+ */
+ public TestNoHostBase(String arg0) {
+ super(arg0);
+ }
+
+ public void setUp() throws Exception{
+ conn = new SimpleHttpConnection();
+ connectionManager = new NoHostHttpConnectionManager();
+ connectionManager.setConnection(conn);
+ client = new HttpClient(connectionManager);
+ client.getHostConfiguration().setHost("localhost", 80, "http");
+ }
+
+}