? diffs-evert.txt Index: java/org/apache/commons/httpclient/Authenticator.java =================================================================== RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/Authenticator.java,v retrieving revision 1.11 diff -u -r1.11 Authenticator.java --- java/org/apache/commons/httpclient/Authenticator.java 5 Jan 2002 11:15:59 -0000 1.11 +++ java/org/apache/commons/httpclient/Authenticator.java 9 Jul 2002 05:12:50 -0000 @@ -70,28 +70,82 @@ *
Utility methods for HTTP authorization and authentication.
** This class provides utility methods for generating - * responses to HTTP authentication challenges. + * responses to HTTP www and proxy authentication challenges. *
* @author Remy Maucherat * @author Rodney Waldhoff * @version $Revision: 1.11 $ $Date: 2002/01/05 11:15:59 $ */ class Authenticator { + /** + * The www authenticate challange header + */ + public static final String WWW_AUTH = "WWW-Authenticate"; + + /** + * The www authenticate response header + */ + public static final String WWW_AUTH_RESP = "Authorization"; + + /** + * The proxy authenticate challange header + */ + public static final String PROXY_AUTH = "Proxy-Authenticate"; + + /** + * The proxy authenticate response header + */ + public static final String PROXY_AUTH_RESP = "Proxy-Authorization"; /** * Add requisite authentication credentials to the given * {@link HttpMethod}, if possible. * + * @see HttpState#setCredentials(String, Credentials) HttpState.setCredentials + * * @param method a {@link HttpMethod} which requires authentication * @param state a {@link HttpState} object providing {@link Credentials} * * @throws HttpException when a parsing or other error occurs * @throws UnsupportedOperationException when the given challenge type is not supported + * @return true if only if a response header was added */ static boolean authenticate(HttpMethod method, HttpState state) throws HttpException { log.debug("Authenticator.authenticate(HttpMethod, HttpState)"); - Header challengeHeader = method.getResponseHeader("WWW-Authenticate"); + + Header challengeHeader = method.getResponseHeader(WWW_AUTH); if(null == challengeHeader) { return false; } + + return authenticate(method, state, challengeHeader, WWW_AUTH_RESP); + } + + /** + * Add requisite proxy authentication credentials to the given + * {@link HttpMethod}, if possible. + * + * @see HttpState#setProxyCredentials(String, Credentials) HttpState.setProxyCredentials + * + * @param method a {@link HttpMethod} which requires authentication + * @param state a {@link HttpState} object providing {@link Credentials} + * + * @throws HttpException when a parsing or other error occurs + * @throws UnsupportedOperationException when the given challenge type is not supported + * @return true if only if a response header was added + */ + static boolean authenticateProxy(HttpMethod method, HttpState state) throws HttpException { + log.debug("Authenticator.authenticateProxy(HttpMethod, HttpState)"); + + Header challengeHeader = method.getResponseHeader(PROXY_AUTH); + if (null == challengeHeader) { return false; } + return authenticate(method, state, challengeHeader, PROXY_AUTH_RESP); + } + + + private static boolean authenticate(HttpMethod method, HttpState state, + Header challengeHeader, + String respHeader) + throws HttpException { + String challenge = challengeHeader.getValue(); if(null == challenge) { return false; } @@ -119,7 +173,7 @@ } String realm = realmstr.substring("realm=\"".length(),realmstr.length()-1); log.debug("Parsed realm \"" + realm + "\" from challenge \"" + challenge + "\"."); - Header header = Authenticator.basic(realm,state); + Header header = Authenticator.basic(realm, state, respHeader); if(null != header) { method.addRequestHeader(header); return true; @@ -137,19 +191,23 @@ * Create a Basic Authorization header for the given * realm and state to the given method. * - * @param method the {@link HttpMethod} to authenticate to * @param realm the basic realm to authenticate to * @param state a {@link HttpState} object providing {@link Credentials} + * @param respHeader the header's name to store the authentication response + * in. PROXY_AUTH_RESP will force the proxy credentials to be used. * - * @return a basic Authorization value + * @return a basic Authorization header * * @throws HttpException when no matching credentials are available */ - static Header basic(String realm, HttpState state) throws HttpException { + static Header basic(String realm, HttpState state, String respHeader) throws HttpException { log.debug("Authenticator.basic(String,HttpState)"); + boolean proxy = PROXY_AUTH_RESP.equals(respHeader); UsernamePasswordCredentials cred = null; try { - cred = (UsernamePasswordCredentials)(state.getCredentials(realm)); + cred = (UsernamePasswordCredentials) ( proxy ? + state.getProxyCredentials(realm) : + state.getCredentials(realm)); } catch(ClassCastException e) { throw new HttpException("UsernamePasswordCredentials required for Basic authentication."); } @@ -158,7 +216,9 @@ log.info("No credentials found for realm \"" + realm + "\", attempting to use default credentials."); } try { - cred = (UsernamePasswordCredentials)(state.getCredentials(null)); + cred = (UsernamePasswordCredentials)( proxy ? + state.getProxyCredentials(null) : + state.getCredentials(null)); } catch(ClassCastException e) { throw new HttpException("UsernamePasswordCredentials required for Basic authentication."); } @@ -166,7 +226,7 @@ if(null == cred) { throw new HttpException("No credentials available for the Basic authentication realm \"" + realm + "\"/"); } else { - return new Header("Authorization",Authenticator.basic(cred)); + return new Header(respHeader, Authenticator.basic(cred)); } } 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.9 diff -u -r1.9 HttpConnection.java --- java/org/apache/commons/httpclient/HttpConnection.java 12 Apr 2002 21:09:20 -0000 1.9 +++ java/org/apache/commons/httpclient/HttpConnection.java 9 Jul 2002 05:12:53 -0000 @@ -66,11 +66,14 @@ import java.net.SocketException; import java.io.InputStream; import java.io.OutputStream; +import java.io.UnsupportedEncodingException; import java.io.IOException; +import javax.net.SocketFactory; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.apache.commons.httpclient.log.*; import java.lang.reflect.Method; +import java.util.HashSet; /** *
@@ -115,7 +118,7 @@
}
/**
- * Fully-specified constructor.
+ * Constructor.
* @param proxyHost the host I should proxy via
* @param proxyPort the port I should proxy via
* @param host the host I should connect to. Parameter value must be non-null.
@@ -123,6 +126,21 @@
* @param secure when true, connect via HTTPS (SSL)
*/
public HttpConnection(String proxyHost, int proxyPort, String host, int port, boolean secure) {
+ this(proxyHost, proxyPort, host, port, secure, null, null);
+ }
+
+ /**
+ * Fully-specified constructor.
+ * @param proxyHost the host I should proxy via
+ * @param proxyPort the port I should proxy via
+ * @param host the host I should connect to. Parameter value must be non-null.
+ * @param port the port I should connect to
+ * @param secure when true, connect via HTTPS (SSL)
+ * @param state the HttpSharedState for establishing connections through authenticated proxies.
+ * @param factory the factory to be used for creating SSL sockets. Or, null for the default.
+ */
+ public HttpConnection(String proxyHost, int proxyPort, String host, int port, boolean secure,
+ HttpSharedState state, SSLSocketFactory factory) {
log.debug("HttpConnection.HttpConnection");
if (host == null) {
throw new NullPointerException("host parameter is null");
@@ -132,6 +150,8 @@
_host = host;
_port = port;
_ssl = secure;
+ _state = state;
+ _sslSocketFactory = factory;
}
// ------------------------------------------ Attribute Setters and Getters
@@ -278,12 +298,26 @@
assertNotOpen(); // ??? is this worth doing?
try {
if (null == _socket) {
- String host = (null == _proxyHost) ? _host : _proxyHost;
- int port = (null == _proxyHost) ? _port : _proxyPort;
if (_ssl) {
- _socket = SSLSocketFactory.getDefault().createSocket(host,port);
+ if (_sslSocketFactory == null)
+ _sslSocketFactory = (SSLSocketFactory)SSLSocketFactory.getDefault();
+ if ((_proxyHost != null) && (_proxyPort > 0)) {
+ // ssl using proxy - create a tunnellec connection
+ doTunnelHandshake(false, null);
+ Socket tunnel = _socket;
+ _socket = _sslSocketFactory.createSocket(tunnel, _host, _port, true);
+ } else {
+ // using with no proxy
+ _socket = _sslSocketFactory.createSocket(_host, _port);
+ }
} else {
+ // non ssl connection
+ String host = (null == _proxyHost) ? _host : _proxyHost;
+ int port = (null == _proxyHost) ? _port : _proxyPort;
_socket = new Socket(host,port);
+ // Not creating a tunnel socket, but authenticating.
+ //if ((_proxyHost != null) && (_proxyPort > 0))
+ // doTunnelHandshake(false, null);
}
}
_socket.setSoTimeout(_so_timeout);
@@ -293,11 +327,115 @@
} catch (IOException e) {
// Connection wasn't opened properly
// so close everything out
+ //log.debug("IOException in opening connection: " + e, e);
closeSocketAndStreams();
throw e;
+ } finally {
+ _attemptedRealms.clear();
}
}
+ private void doTunnelHandshake(boolean authenticate, String realm) throws IOException
+ {
+ log.debug("doTunnelHandshake(authenticate='" + authenticate + "', realm='" + realm + "')");
+ _attemptedRealms.add(realm);
+ String authorizationHeader = null;
+ if (authenticate && _state != null) {
+ Credentials credentials = _state.getProxyCredentials(realm);
+ if ((credentials != null) &&
+ (credentials instanceof UsernamePasswordCredentials)) {
+ try {
+ authorizationHeader = Authenticator.basic((UsernamePasswordCredentials)credentials);
+ } catch (HttpException ex) {
+ log.debug("doTunnelHandshake(): Exception reading credentials: " + ex);
+ }
+ }
+ else if (realm != null) {
+ // Try it once with the default credentials that are stored
+ // with a null realm.
+ doTunnelHandshake(true, null);
+ return;
+ }
+ }
+ _socket = new Socket(_proxyHost, _proxyPort);
+ _output = _socket.getOutputStream();
+ String msg = "CONNECT " + _host + ":" + _port + " HTTP/1.0\r\n" +
+ "User-Agent: " + sun.net.www.protocol.http.HttpURLConnection.userAgent + "\r\n" +
+ //"Host: " + _host + ":" + _port + "\r\n" +
+ "Host: " + _host + "\r\n" +
+ "Content-Length: 0\r\n" +
+ "Proxy-Connection: Keep-Alive\r\n" +
+ "Pragma: no-cache\r\n";
+ if (authorizationHeader != null)
+ msg += "Proxy-Authorization: " + authorizationHeader + "\r\n";
+ msg += "\r\n";
+
+ wireLog.info(">>\r\n" + msg);
+
+ byte b[];
+ try {
+ b = msg.getBytes("ASCII7");
+ } catch (UnsupportedEncodingException ignored) {
+ b = msg.getBytes();
+ }
+ _output.write(b);
+ _output.flush();
+
+ _input = _socket.getInputStream();
+ boolean error = false;
+
+ _open = true;
+ StringBuffer replyBuffer = new StringBuffer();
+ String proxyAuthenticate = null;
+ String firstLine = null;
+ while (true) {
+ String line = readLine();
+ if (line == null || line.length() < 1)
+ break;
+ if (firstLine == null)
+ firstLine = line;
+ replyBuffer.append(line + "\r\n");
+ int colonPosition = line.indexOf(":");
+ if (colonPosition > -1) {
+ String key = line.substring(0, colonPosition);
+ String value = line.substring(colonPosition);
+ if (key.trim().equalsIgnoreCase("Proxy-Authenticate"))
+ proxyAuthenticate = value;
+ }
+ }
+ String replyStr = replyBuffer.toString();
+ wireLog.info("<<\r\n" + replyStr);
+ _open = false;
+
+ String newRealm = null;
+ if (proxyAuthenticate != null) {
+ int start = proxyAuthenticate.indexOf("\"") + 1;
+ int end = proxyAuthenticate.lastIndexOf("\"");
+ if (start > -1 && end > -1 && start != end)
+ newRealm = proxyAuthenticate.substring(start, end);
+ }
+
+ if (newRealm != null && !_attemptedRealms.contains(newRealm)) {
+ //closeSocketAndStreams();
+ doTunnelHandshake(true, newRealm);
+ return;
+ }
+
+ log.debug("doTunnelHandshake(): Reply: " + replyStr);
+
+ /* We asked for HTTP/1.0, so we should get that back */
+ if (!replyStr.startsWith("HTTP/1.0 200")) {
+ msg = "Unable to tunnel through "
+ + _proxyHost + ":" + _proxyPort
+ + ". Proxy returns \"" + firstLine + "\"";
+ log.debug("doTunnelHandshake(): " + msg);
+ throw new IOException(msg);
+ }
+
+ /* tunneling Handshake was successful! */
+
+ }
+
/**
* Return a {@link RequestOutputStream}
* suitable for writing (possibly chunked)
@@ -347,11 +485,17 @@
}
/**
- * Write the specified bytes to my output stream.
+ * Write the specified bytes to my output stream. The first byte written is
+ * data[off]. len bytes of data will
+ * be written.
+ *
+ * @param data The data that will be written
+ * @param off Offset
+ * @param len Length
* @throws IllegalStateException if I am not connected
* @throws IOException if an I/O problem occurs
*/
- public void write(byte[] data) throws IOException, IllegalStateException, HttpRecoverableException {
+ public void write(byte[] data, int off, int len) throws IOException, IllegalStateException, HttpRecoverableException {
if(log.isDebugEnabled()){
log.debug("HttpConnection.write(byte[])");
}
@@ -360,7 +504,7 @@
wireLog.info(">> \"" + new String(data) + "\"");
}
try {
- _output.write(data);
+ _output.write(data, off, len);
} catch(SocketException e){
if(log.isDebugEnabled()) {
log.debug("HttpConnection: Socket exception while writing data",e);
@@ -374,6 +518,15 @@
}
}
+ /**
+ * Write the specified bytes to my output stream.
+ * @param data The data that will be written
+ * @throws IllegalStateException if I am not connected
+ * @throws IOException if an I/O problem occurs
+ */
+ public void write(byte[] data) throws IOException, IllegalStateException, HttpRecoverableException {
+ write(data, 0, data.length);
+ }
/**
* Write the specified bytes, followed by
@@ -593,5 +746,10 @@
private static final byte[] CRLF = "\r\n".getBytes();
/** SO_TIMEOUT value */
private int _so_timeout = 0;
-
+ /** The factory to be used for creating SSL sockets. **/
+ private SSLSocketFactory _sslSocketFactory = null;
+ /** The state to be used for establishing connections through authenticated proxies. **/
+ private HttpSharedState _state = null;
+ /** All the realms for which authentication has already been attempted once. **/
+ private HashSet _attemptedRealms = new HashSet();
}
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.5
diff -u -r1.5 HttpConnectionManager.java
--- java/org/apache/commons/httpclient/HttpConnectionManager.java 15 Apr 2002 18:35:29 -0000 1.5
+++ java/org/apache/commons/httpclient/HttpConnectionManager.java 9 Jul 2002 05:12:55 -0000
@@ -67,6 +67,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.LinkedList;
+import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.httpclient.log.*;
@@ -87,6 +88,8 @@
private int maxConnections = 2; // Per RFC 2616 sec 8.1.4
private String proxyHost = null;
private int proxyPort = -1;
+ private HttpSharedState state = null;
+ private SSLSocketFactory factory = null;
/**
* No-args constructor
@@ -96,6 +99,15 @@
}
/**
+ * This constructor is necessary for making connections through authenticated proxies.
+ * @param state for making connections through authenticated proxies, or null.
+ */
+ public HttpConnectionManager(HttpSharedState state)
+ {
+ this.state = state;
+ }
+
+ /**
* Set the proxy host to use for all connections.
*
* @param proxyHost - the proxy host name
@@ -157,6 +169,15 @@
}
/**
+ * Allows you to specify a new factory to be used as the default
+ * for creating SSL sockets. Setting the factory to null will
+ * reset it to using SSLSocketFactory.getDefault().
+ */
+ public void setSSLSocketFactory(SSLSocketFactory factory) {
+ this.factory = factory;
+ }
+
+ /**
* 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
@@ -249,7 +270,7 @@
if(log.isDebugEnabled()){
log.debug("HttpConnectionManager.getConnection: creating connection for " + host + ":" + port + " via " + proxyHost + ":" + proxyPort);
}
- conn = new HttpConnection(proxyHost, proxyPort, host, port, isSecure);
+ conn = new HttpConnection(proxyHost, proxyPort, host, port, isSecure, state, factory);
numConnections = new Integer(numConnections.intValue()+1);
mapNumConnections.put(key, numConnections);
}else{
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.12
diff -u -r1.12 HttpMethod.java
--- java/org/apache/commons/httpclient/HttpMethod.java 22 Feb 2002 19:15:54 -0000 1.12
+++ java/org/apache/commons/httpclient/HttpMethod.java 9 Jul 2002 05:12:56 -0000
@@ -262,6 +262,29 @@
public InputStream getResponseBodyAsStream() throws IOException;
/**
+ * Returns the time in milliseconds when a connection was
+ * available and open.
+ * If an error occurred before this event, then it will return -1.
+ */
+ public long getWhenConnectedMillis();
+
+ /**
+ * Returns the time in milliseconds when the headers of the request
+ * were sent, not the body of the request. It might first wait for
+ * a 100 response before sending the body of the request.
+ * If an error occurred before this event, then it will return -1.
+ */
+ public long getWhenRequestedMillis();
+
+ /**
+ * Returns the time in milliseconds when the first response was received
+ * from the server, regardless of whether it was a success, failure
+ * or continue response.
+ * If an error occurred before this event, then it will return -1.
+ */
+ public long getWhenRespondedMillis();
+
+ /**
* Return true if I have been {@link #execute executed}
* but not recycled.
*/
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.28
diff -u -r1.28 HttpMethodBase.java
--- java/org/apache/commons/httpclient/HttpMethodBase.java 16 Apr 2002 14:30:42 -0000 1.28
+++ java/org/apache/commons/httpclient/HttpMethodBase.java 9 Jul 2002 05:13:03 -0000
@@ -267,7 +267,6 @@
* Remove the request header associated with the given name.
* Note that header-name matching is case insensitive.
* @param headerName the header name
- * @return the header
*/
public void removeRequestHeader(String headerName) {
requestHeaders.remove(headerName.toLowerCase());
@@ -405,6 +404,35 @@
}
/**
+ * Returns the time in milliseconds when a connection was
+ * available and open.
+ * If an error occurred before this event, then it will return -1.
+ */
+ public long getWhenConnectedMillis() {
+ return whenConnected;
+ }
+
+ /**
+ * Returns the time in milliseconds when the headers of the request
+ * were sent, not the body of the request. It might first wait for
+ * a 100 response before sending the body of the request.
+ * If an error occurred before this event, then it will return -1.
+ */
+ public long getWhenRequestedMillis() {
+ return whenRequested;
+ }
+
+ /**
+ * Returns the time in milliseconds when the first response was received
+ * from the server, regardless of whether it was a success, failure
+ * or continue response.
+ * If an error occurred before this event, then it will return -1.
+ */
+ public long getWhenRespondedMillis() {
+ return whenResponded;
+ }
+
+ /**
* Return true if I have been {@link #execute executed}
* but not recycled.
*/
@@ -450,6 +478,7 @@
Set visited = new HashSet();
Set realms = new HashSet();
+ Set proxyRealms = new HashSet();
int retryCount = 0;
for(;;) {
visited.add(connection.getHost() + ":" + connection.getPort() + "|" + HttpMethodBase.generateRequestLine(connection, getName(),getPath(),getQueryString(),(http11 ? "HTTP/1.1" : "HTTP/1.0")));
@@ -466,11 +495,20 @@
connection.open();
}
+ if (whenConnected == -1)
+ whenConnected = System.currentTimeMillis();
+
writeRequest(state,connection);
used = true;
+ if (whenRequested == -1)
+ whenRequested = System.currentTimeMillis();
+
// need to close output?, but when?
readResponse(state,connection);
+
+ if (whenResponded == -1)
+ whenResponded = System.currentTimeMillis();
}catch(HttpRecoverableException e){
if(retryCount >= maxRetries){
throw new HttpException(e.toString());
@@ -505,22 +543,45 @@
}
}
- if (HttpStatus.SC_UNAUTHORIZED == statusCode) {
- Header wwwauth = getResponseHeader("WWW-Authenticate");
+ if ((HttpStatus.SC_UNAUTHORIZED == statusCode)
+ || (HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED == statusCode)) {
+
+ Header wwwauth = null;
+ Set realmsUsed = null;
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ wwwauth = getResponseHeader(Authenticator.WWW_AUTH);
+ realmsUsed = realms;
+ break;
+
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ wwwauth = getResponseHeader(Authenticator.PROXY_AUTH);
+ realmsUsed = proxyRealms;
+ break;
+ }
if (null != wwwauth) {
String pathAndCreds = getPath() + ":" + wwwauth.getValue();
- if (realms.contains(pathAndCreds)) {
+ if (realmsUsed.contains(pathAndCreds)) {
if (log.isInfoEnabled()) {
log.info("Already tried to authenticate to \"" + wwwauth.getValue() + "\" but still receiving " + HttpStatus.SC_UNAUTHORIZED + ".");
}
break;
} else {
- realms.add(pathAndCreds);
+ realmsUsed.add(pathAndCreds);
}
boolean authenticated = false;
try {
- authenticated = Authenticator.authenticate(this,state);
+ switch (statusCode) {
+ case HttpStatus.SC_UNAUTHORIZED:
+ authenticated = Authenticator.authenticate(this,state);
+ break;
+
+ case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
+ authenticated = Authenticator.authenticateProxy(this,state);
+ break;
+ }
+
} catch(HttpException e) {
// ignored
}
@@ -539,119 +600,11 @@
continue;
}
}
- } else if (HttpStatus.SC_MOVED_TEMPORARILY == statusCode ||
- HttpStatus.SC_MOVED_PERMANENTLY == statusCode ||
- HttpStatus.SC_TEMPORARY_REDIRECT == statusCode) {
- if (getFollowRedirects()) {
- //
- // Note that we cannot current support
- // redirects that change the HttpConnection
- // parameters (host, port, protocol)
- // because we don't yet have a good way to
- // get the new connection.
- //
- // For the time being, we just return
- // the 302 response, and allow the user
- // agent to resubmit if desired.
- //
- Header location = getResponseHeader("location");
- if (location != null) {
- URL url = null;
- try {
- if (location.getValue().startsWith("/")) {
- if (log.isDebugEnabled()) {
- log.debug("Following relative Location header \"" + location + "\".");
- }
- String protocol = connection.isSecure() ? "https" : "http";
- int port = connection.getPort();
- if (-1 == port) {
- port = connection.isSecure() ? 443 : 80;
- }
- url = new URL(protocol,connection.getHost(),port,location.getValue());
- } else if(!isStrictMode() && location.getValue().indexOf("://") < 0) {
- /*
- * Location doesn't start with / but it doesn't contain a protocol.
- * Per RFC 2616, that's an error. In non-strict mode we'll try
- * to build a URL relative to the current path.
- */
- String protocol = connection.isSecure() ? "https" : "http";
- int port = connection.getPort();
- if(-1 == port) {
- port = connection.isSecure() ? 443 : 80;
- }
- URL currentUrl = new URL(protocol,connection.getHost(),port,getPath());
- url = new URL(currentUrl, location.getValue());
- } else {
- url = new URL(location.getValue());
- }
- } catch(MalformedURLException e) {
- log.error("Exception while parsing location header \"" + location + "\"",e);
- throw new HttpException(e.toString());
- }
- if ("http".equalsIgnoreCase(url.getProtocol())) {
- if (connection.isSecure()) {
- log.info("Server is attempting to redirect an HTTPS request to an HTTP one.");
- throw new HttpException("Server is attempting to redirect an HTTPS request to an HTTP one.");
- }
- } else if ("https".equalsIgnoreCase(url.getProtocol())) {
- if (!connection.isSecure()) {
- log.info("Server is attempting to convert an HTTP request to an HTTP one, which is currently not supported. Returning " + statusCode + ".");
- break;
- }
- }
- if (!connection.getHost().equalsIgnoreCase(url.getHost())) {
- log.info("Server is attempting to redirect a different host, which is currently not supported. Returning " + statusCode + ".");
- break;
- }
- if (url.getPort() == -1) {
- if (connection.isSecure()) {
- if (connection.getPort() != 443) {
- log.info("Server is attempting to redirect a different port, which is currently not supported. Returning " + statusCode + ".");
- break;
- }
- } else {
- if (connection.getPort() != 80) {
- log.info("Server is attempting to redirect a different port, which is currently not supported. Returning " + statusCode + ".");
- break;
- }
- }
- } else if (connection.getPort() != url.getPort()) {
- log.info("Server is attempting to redirect a different port, which is currently not supported. Returning " + statusCode + ".");
- break;
- }
- String absolutePath = URIUtil.getPath(url.toString());
- String qs = URIUtil.getQueryString(url.toString());
-
- // if we haven't already, let's try it again with the new path
- if (visited.contains(connection.getHost() + ":" + connection.getPort() + "|" + HttpMethodBase.generateRequestLine(connection, getName(),absolutePath,qs,(http11 ? "HTTP/1.1" : "HTTP/1.0")))) {
- throw new HttpException("Redirect going into a loop, visited \"" + absolutePath + "\" already.");
- } else {
- if (log.isDebugEnabled()) {
- log.debug("Changing path from \"" + getPath() + "\" to \"" + absolutePath + "\" in response to " + statusCode + " response.");
- log.debug("Changing query string from \"" + getQueryString() + "\" to \"" + qs + "\" in response to " + statusCode + " response.");
- }
- setPath(URIUtil.decode(absolutePath));
- setQueryString(qs);
- continue;
- }
- } else {
- // got a redirect response, but no location header
- if (log.isInfoEnabled()) {
- log.info("HttpMethodBase.execute(): Received " + statusCode + " response, but no \"Location\" header. Returning " + statusCode + ".");
- }
- break;
- }
- } else {
- // got a redirect response,
- // but followRedirects is false
- log.info("HttpMethodBase.execute(): Received " + statusCode + " response, but followRedirects is false. Returning " + statusCode + ".");
- break;
- }
- } else {
- // neither an UNAUTHORIZED nor a redirect response
- // so exit
- break;
}
+ // Moved the handling of redirects to HttpMultiClient. Replaced all the
+ // code to build a URL relative to the original URL with:
+ // URL newUrl = new URL( URL context, String newUrlStr ) --Evert
+ break;
}
return statusCode;
@@ -767,6 +720,7 @@
addHostRequestHeader(state,conn);
addCookieRequestHeader(state,conn);
addAuthorizationRequestHeader(state,conn);
+ addProxyAuthorizationRequestHeader(state, conn);
addContentLengthRequestHeader(state,conn);
}
@@ -810,8 +764,8 @@
*/
protected void addAuthorizationRequestHeader(HttpState state, HttpConnection conn) throws IOException, HttpException {
// add authorization header, if needed
- if (!requestHeaders.containsKey("authorization")) {
- Header wwwAuthenticateHeader = (Header)(responseHeaders.get("www-authenticate"));
+ if (!requestHeaders.containsKey(Authenticator.WWW_AUTH_RESP.toLowerCase())) {
+ Header wwwAuthenticateHeader = (Header)(responseHeaders.get(Authenticator.WWW_AUTH.toLowerCase()));
if (null != wwwAuthenticateHeader) {
try {
Authenticator.authenticate(this,state);
@@ -823,6 +777,25 @@
}
/**
+ * Adds a Proxy-Authorization request if needed,
+ * as long as no Proxy-Authorization request header
+ * already exists.
+ */
+ protected void addProxyAuthorizationRequestHeader(HttpState state, HttpConnection conn) throws IOException, HttpException {
+ // add authorization header, if needed
+ if (!requestHeaders.containsKey(Authenticator.PROXY_AUTH_RESP.toLowerCase())) {
+ Header wwwAuthenticateHeader = (Header)(responseHeaders.get(Authenticator.PROXY_AUTH.toLowerCase()));
+ if (null != wwwAuthenticateHeader) {
+ try {
+ Authenticator.authenticateProxy(this,state);
+ } catch(HttpException e) {
+ // ignored
+ }
+ }
+ }
+ }
+
+ /**
* Adds a Content-Length or
* Transer-Encoding: Chunked request header,
* as long as no Content-Length request header
@@ -1239,6 +1212,9 @@
http11 = true;
bodySent = false;
responseBody = null;
+ whenConnected = -1;
+ whenRequested = -1;
+ whenResponded = -1;
}
// ---------------------------------------------- Protected Utility Methods
@@ -1299,14 +1275,15 @@
return (name + " " + buf.toString() + " " + protocol + "\r\n");
} else {
if (connection.isSecure()) {
- return (name +
- " https://" +
- connection.getHost() +
- ((443 == connection.getPort() || -1 == connection.getPort()) ? "" : (":" + connection.getPort()) ) +
- buf.toString() +
- " " +
- protocol +
- "\r\n");
+// return (name +
+// " https://" +
+// connection.getHost() +
+// ((443 == connection.getPort() || -1 == connection.getPort()) ? "" : (":" + connection.getPort()) ) +
+// buf.toString() +
+// " " +
+// protocol +
+// "\r\n");
+ return (name + " " + buf.toString() + " " + protocol + "\r\n");
} else {
return (name +
" http://" +
@@ -1367,6 +1344,12 @@
private int maxRetries = 3;
/** True if we're in strict mode. */
private boolean strictMode = true;
+ /** The moment when an open connection is available. **/
+ private long whenConnected = -1;
+ /** The moment when the request headers have been sent, not the request body. **/
+ private long whenRequested = -1;
+ /** The moment of the first response received back from the server. **/
+ private long whenResponded = -1;
// -------------------------------------------------------------- Constants
Index: java/org/apache/commons/httpclient/HttpMultiClient.java
===================================================================
RCS file: /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMultiClient.java,v
retrieving revision 1.5
diff -u -r1.5 HttpMultiClient.java
--- java/org/apache/commons/httpclient/HttpMultiClient.java 5 Jul 2002 07:13:46 -0000 1.5
+++ java/org/apache/commons/httpclient/HttpMultiClient.java 9 Jul 2002 05:13:05 -0000
@@ -63,8 +63,13 @@
package org.apache.commons.httpclient;
import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import javax.net.ssl.SSLSocketFactory;
import org.apache.commons.httpclient.log.*;
+import org.apache.commons.httpclient.methods.GetMethod;
+import org.apache.commons.httpclient.methods.PostMethod;
/**
*
@@ -85,7 +90,7 @@
// ----------------------------------------------------- Instance Variables
private HttpSharedState state = null;
- private HttpConnectionManager mgr = new HttpConnectionManager();
+ private HttpConnectionManager mgr = null;
private boolean strictMode = true;
private int timeoutConnection = 0;
private int timeoutRequest = 0;
@@ -100,6 +105,7 @@
*/
public HttpMultiClient()
{
+ mgr = new HttpConnectionManager(getState());
}
/**
@@ -266,6 +272,26 @@
}
/**
+ * 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)
+ {
+ mgr.setMaxConnectionsPerHost(maxConnections);
+ }
+
+ /**
+ * Allows you to specify a new factory to be used as the default
+ * for creating SSL sockets. Setting the factory to null will
+ * reset it to using SSLSocketFactory.getDefault().
+ */
+ public void setSSLSocketFactory(SSLSocketFactory factory) {
+ mgr.setSSLSocketFactory(factory);
+ }
+
+ /**
*
* Execute the given {@link HttpUrlMethod} using my current
* {@link HttpConnection connection} and {@link HttpState}.
@@ -279,12 +305,21 @@
*/
public int executeMethod(HttpUrlMethod method) throws IOException, HttpException
{
+ return executeMethod(method, 0);
+ }
+ protected int executeMethod(HttpUrlMethod method, int numberOfRedirects)
+ throws IOException, HttpException
+ {
if (null == method)
{
throw new NullPointerException("method parameter");
}
+ if(numberOfRedirects > 10)
+ {
+ throw new HttpException("Redirected more than 10 times.");
+ }
HttpConnection connection = mgr.getConnection(method.getUrl(), timeoutConnection);
connection.setSoTimeout(timeoutRequest);
@@ -309,8 +344,13 @@
mgr.releaseConnection(connection);
}
- if (status == 301 || status == 302 ||
- status == 303 || status == 307)
+ // Excluding post methods from redirects. Paragraph 10.3
+ // of HTTP/1.1 RFC2616 says that any method other than GET or HEAD
+ // requires user intervention before redirecting. --Evert
+ if (method.getFollowRedirects() &&
+ (method instanceof GetMethod || method instanceof PostMethod) &&
+ (status == 301 || status == 302 ||
+ status == 303 || status == 307))
{
Header header = method.getResponseHeader("Location");
String url = header.getValue();
@@ -319,13 +359,34 @@
log.error("HttpMultiClient.executeMethod: Received redirect without Location header.");
throw new HttpException("Received redirect without Location header.");
}
-
+
+ log.debug("HttpMultiClient.executeMethod: Following redirect to: " + url);
+
+ String oldUrlStr = method.getUrl();
+ //String oldRequestBody = null;
+ //if (method instanceof PostMethod)
+ //{
+ // oldRequestBody = ((PostMethod)method).getRequestBody();
+ //}
+ URL oldUrl = null;
+ try
+ {
+ oldUrl = new URL(oldUrlStr);
+ } catch (MalformedURLException e)
+ {
+ // This means the original url was also malformed. But, if that
+ // was the case we should never have gotten this far.
+ log.debug("HttpMultiClient.executemethod: The original url was malformed: " + e);
+ throw e;
+ }
+ URL newUrl = new java.net.URL(oldUrl, url);
method.recycle();
- method.setUrl(url);
- return executeMethod(method);
+ method.setUrl(newUrl.toString());
+ //if (method instanceof PostMethod)
+ // ((PostMethod)method).setRequestBody(oldRequestBody);
+ return executeMethod(method, numberOfRedirects++);
}
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.5
diff -u -r1.5 HttpState.java
--- java/org/apache/commons/httpclient/HttpState.java 5 Jan 2002 11:16:00 -0000 1.5
+++ java/org/apache/commons/httpclient/HttpState.java 9 Jul 2002 05:13:06 -0000
@@ -92,6 +92,11 @@
private HashMap credMap = new HashMap();
/**
+ * My proxy {@link Credentials Credentials}, by realm.
+ */
+ private HashMap proxyCred = new HashMap();
+
+ /**
* My {@link Cookie Cookie}s.
*/
private ArrayList cookies = new ArrayList();
@@ -226,4 +231,40 @@
return (Credentials)(credMap.get(realm));
}
+ /**
+ *
+ * Set the {@link Credentials} for the proxy with the given + * authentication realm. + *
+ *
+ * When realm is null, I'll use the given
+ * credentials when no other {@link Credentials} have
+ * been supplied for the given challenging realm.
+ * (I.e., use a null realm to set the "default"
+ * credentials.) Realms rarely make much sense with proxies, so
+ * null is normally a good choice here.
+ *
+ * Get the {@link Credentials} for the proxy with the given authentication + * realm. + *
+ *
+ * When realm is null, I'll return the
+ * "default" credentials.
+ * (See {@link #setCredentials setCredentials}.)
+ *
QueryString=\"foo=bar&bar=foo\"
") >= 0); - assertTrue(method.getResponseBodyAsString().indexOf("name=\"para\";value=\"meter\"") >= 0); - assertTrue(method.getResponseBodyAsString().indexOf("name=\"param\";value=\"eter\"") >= 0); + assertEquals(HttpStatus.SC_MOVED_TEMPORARILY,method.getStatusCode()); } public void testPutRedirect() throws Exception { @@ -226,8 +222,7 @@ t.printStackTrace(); fail("Unable to execute method : " + t.toString()); } - assertTrue(method.getResponseBodyAsString(),method.getResponseBodyAsString().indexOf("This is data to be sent in the body of an HTTP PUT.") >= 0); - assertEquals(200,method.getStatusCode()); + assertEquals(HttpStatus.SC_MOVED_TEMPORARILY,method.getStatusCode()); } }