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.11 diff -u -r1.11 ConnectMethod.java --- java/org/apache/commons/httpclient/ConnectMethod.java 4 Apr 2003 02:37:02 -0000 1.11 +++ java/org/apache/commons/httpclient/ConnectMethod.java 25 Apr 2003 15:16:13 -0000 @@ -65,16 +65,16 @@ import java.io.IOException; -import org.apache.commons.httpclient.auth.HttpAuthenticator; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** *

Wraps another method to tunnel through a proxy.

* - * @author Ortwin Gl�ck + * @author Ortwin Glueck * @author dIon Gillard * @author Mike Bowler + * @author Oleg Kalnichevski * @since 2.0 * @version $Revision: 1.11 $ $Date: 2003/04/04 02:37:02 $ */ @@ -102,6 +102,92 @@ return NAME; } + + /** + * This method does nothing. CONNECT request is not supposed + * to contain Authorization request header. + * + * @param state current state of http requests + * @param conn the connection to use for I/O + * + * @throws IOException when errors occur reading or writing to/from the + * connection + * @throws HttpException when a recoverable error occurs + * + * @see HttpMethodBase#addAuthorizationRequestHeader(HttpState, HttpConnection) + */ + protected void addAuthorizationRequestHeader(HttpState state, HttpConnection conn) + throws IOException, HttpException + { + // Do nothing. Not applicable to CONNECT method + } + + /** + * This method does nothing. CONNECT request is not supposed + * to contain Content-Length request header. + * + * @param state current state of http requests + * @param conn the connection to use for I/O + * + * @throws IOException when errors occur reading or writing to/from the + * connection + * @throws HttpException when a recoverable error occurs + * + * @see HttpMethodBase#addContentLengthRequestHeader(HttpState, HttpConnection) + */ + protected void addContentLengthRequestHeader(HttpState state, HttpConnection conn) + throws IOException, HttpException + { + // Do nothing. Not applicable to CONNECT method + } + + /** + * This method does nothing. CONNECT request is not supposed + * to contain Cookie request header. + * + * @param state current state of http requests + * @param conn the connection to use for I/O + * + * @throws IOException when errors occur reading or writing to/from the + * connection + * @throws HttpException when a recoverable error occurs + * + * @see HttpMethodBase#addCookieRequestHeader(HttpState, HttpConnection) + */ + protected void addCookieRequestHeader(HttpState state, HttpConnection conn) + throws IOException, HttpException + { + // Do nothing. Not applicable to CONNECT method + } + + + /** + * Populates the request headers map to with additional {@link Header + * headers} to be submitted to the given {@link HttpConnection}. + * + *

+ * This implementation adds User-Agent, Host, + * and Proxy-Authorization headers, when appropriate. + *

+ * + * @param state the client state + * @param conn the {@link HttpConnection} the headers will eventually be + * written to + * @throws IOException when an error occurs writing the request + * @throws HttpException when a HTTP protocol error occurs + * + * @see #writeRequestHeaders + */ + protected void addRequestHeaders(HttpState state, HttpConnection conn) + throws IOException, HttpException + { + LOG.trace("enter ConnectMethod.addRequestHeaders(HttpState, " + + "HttpConnection)"); + addUserAgentRequestHeader(state, conn); + addHostRequestHeader(state, conn); + addProxyAuthorizationRequestHeader(state, conn); + } + /** * Execute this method by tunnelling and then executing the wrapped method. * @@ -120,42 +206,29 @@ if ((code >= 200) && (code < 300)) { conn.tunnelCreated(); code = method.execute(state, conn); - } - return code; - } - - /** - * Writes a minimal set of headers to the proxy. - * - * @param state the current http state - * @param conn the connection to write to - * @throws HttpException when an error occurs writing the headers - * @throws IOException when an error occurs writing the headers - */ - protected void writeRequestHeaders(HttpState state, HttpConnection conn) - throws HttpException, IOException { - LOG.trace("enter ConnectMethod.writeRequestHeaders(HttpState, " - + "HttpConnection)"); - - if (method instanceof HttpMethodBase) { - ((HttpMethodBase) method).addRequestHeaders(state, conn); - } - String line = method.getRequestHeader("Host").toExternalForm(); - conn.print(line); - if (Wire.enabled()) { - Wire.output(line); - } - Header header = method.getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); - if (header == null) { - header = getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP); - } - if (header != null) { - line = header.toExternalForm(); - conn.print(line); - if (Wire.enabled()) { - Wire.output(line); + } 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("Release connection and fake the response for the original method"); + releaseConnection(); + if (method instanceof HttpMethodBase ) { + // This sucks + ((HttpMethodBase)method).fakeResponse( + this.getStatusLine(), + this.getResponseHeaderGroup()); } } + return code; } /** @@ -170,22 +243,22 @@ throws IOException, HttpException { int port = conn.getPort(); if (port == -1) { - port = conn.isSecure() ? 443 : 80; + port = conn.getProtocol().getDefaultPort(); + } + StringBuffer buffer = new StringBuffer(); + buffer.append(getName()); + buffer.append(' '); + buffer.append(conn.getHost()); + if (port > -1) { + buffer.append(':'); + buffer.append(port); } - String line = getName() + " " + conn.getHost() + ":" + port - + " HTTP/1.1"; + buffer.append(" HTTP/1.1"); + String line = buffer.toString(); conn.printLine(line); if (Wire.enabled()) { Wire.output(line); } - } - - /** - * Does nothing. This should be handled by the actual method. - * - * @see HttpMethodBase#responseBodyConsumed() - */ - protected void responseBodyConsumed() { } /** 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.58 diff -u -r1.58 HttpConnection.java --- java/org/apache/commons/httpclient/HttpConnection.java 22 Apr 2003 18:11:01 -0000 1.58 +++ java/org/apache/commons/httpclient/HttpConnection.java 25 Apr 2003 15:16:13 -0000 @@ -451,7 +451,7 @@ * @return true if I am connected */ public boolean isOpen() { - if (isStale()) { + if (used && isStale()) { LOG.debug("Connection is stale, closing..."); close(); } 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.136 diff -u -r1.136 HttpMethodBase.java --- java/org/apache/commons/httpclient/HttpMethodBase.java 22 Apr 2003 17:25:40 -0000 1.136 +++ java/org/apache/commons/httpclient/HttpMethodBase.java 25 Apr 2003 15:16:14 -0000 @@ -860,25 +860,48 @@ /** * Return true if we should close the connection now. The connection will - * only be left open if we are using HTTP1.1 + * only be left open if we are using HTTP1.1 or if "Connection: keep-alive" + * was sent. + * * @return boolean true if we should close the connection. */ - protected boolean shouldCloseConnection() { - if (!http11) { - LOG.debug("Should close connection since using HTTP/1.0."); - return true; + protected boolean shouldCloseConnection(HttpConnection conn) { + + Header connectionHeader = null; + if (conn.isTransparent()) { + connectionHeader = getResponseHeader("connection"); } else { - Header connectionHeader = getResponseHeader("connection"); - if (null != connectionHeader - && "close".equalsIgnoreCase(connectionHeader.getValue())) { - LOG.debug("Should close connection since \"Connection: close\" header found."); + connectionHeader = getResponseHeader("proxy-connection"); + } + if (connectionHeader != null) { + if (connectionHeader.getValue().equalsIgnoreCase("close")) { + if (LOG.isDebugEnabled()) { + LOG.debug("Should close connection in response to " + + connectionHeader.toExternalForm()); + } return true; + } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) { + if (LOG.isDebugEnabled()) { + LOG.debug("Should NOT close connection in response to " + + connectionHeader.toExternalForm()); + } + return false; + } else { + if (LOG.isDebugEnabled()) { + LOG.debug("Unknown directive: " + connectionHeader.toExternalForm()); + } } } - return false; + LOG.debug("Resorting to protocol version default close connection policy"); + // missing or invalid connection header, do the default + if (http11) { + LOG.debug("Should NOT close connection, using HTTP/1.1."); + } else { + LOG.debug("Should close connection, using HTTP/1.0."); + } + return !http11; } - /** * Return true if a retry is needed. * @param statusCode The status code @@ -1914,9 +1937,7 @@ } else { int expectedLength = getResponseContentLength(); if (expectedLength == -1) { - if (canResponseHaveBody(statusLine.getStatusCode()) - && !getName().equalsIgnoreCase("connect") - ) { + if (canResponseHaveBody(statusLine.getStatusCode())) { result = is; } } else { @@ -2527,7 +2548,7 @@ responseStream = null; responseConnection.setLastResponseInputStream(null); - if (shouldCloseConnection()) { + if (shouldCloseConnection(responseConnection)) { responseConnection.close(); } @@ -2591,6 +2612,20 @@ */ public void setMethodRetryHandler(MethodRetryHandler handler) { methodRetryHandler = handler; + } + + /** + * This method is a dirty hack intended to work around + * current (2.0) design flaw that prevents the user from + * obraining correct status code and headers from the + * preceding HTTP CONNECT method. + * + * TODO: Remove this crap as soon as possible + */ + + protected void fakeResponse(StatusLine statusline, HeaderGroup responseheaders) { + this.statusLine = statusline; + this.responseHeaders = responseheaders; } }