marcsaeg 02/02/22 11:20:35 Modified: httpclient/src/java/org/apache/commons/httpclient HttpMethodBase.java Log: 1) Support new HttpRecoverableException. 2) Fixed handling of 100 response after the body was sent. 3) Support for relative URIs in Location: header in non-strict mode. 4) Always recreate cookie headers. A redirect may have added new cookies or altered the path. 5) Updated readResponseBody() to handle servers that send bodies without the expected header elements. 6) Added a readResponseBody() method that takes an OutputStream. Derived classes can now more easily use the readResponseBody() method from the base class. Revision Changes Path 1.26 +132 -30 jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java Index: HttpMethodBase.java =================================================================== RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v retrieving revision 1.25 retrieving revision 1.26 diff -u -r1.25 -r1.26 --- HttpMethodBase.java 17 Feb 2002 12:04:29 -0000 1.25 +++ HttpMethodBase.java 22 Feb 2002 19:20:35 -0000 1.26 @@ -1,7 +1,7 @@ /* - * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.25 2002/02/17 12:04:29 dion Exp $ - * $Revision: 1.25 $ - * $Date: 2002/02/17 12:04:29 $ + * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.26 2002/02/22 19:20:35 marcsaeg Exp $ + * $Revision: 1.26 $ + * $Date: 2002/02/22 19:20:35 $ * ==================================================================== * * The Apache Software License, Version 1.1 @@ -111,7 +111,7 @@ * @author Rodney Waldhoff * @author Sean C. Sullivan * @author <a href="mailto:[EMAIL PROTECTED]">dIon Gillard</a> - * @version $Revision: 1.25 $ $Date: 2002/02/17 12:04:29 $ + * @version $Revision: 1.26 $ $Date: 2002/02/22 19:20:35 $ */ public abstract class HttpMethodBase implements HttpMethod { @@ -158,6 +158,33 @@ } /** + * Turns strict mode on or off. In strict mode (the default) + * we following the letter of RFC 2616, the Http 1.1 specification. + * If strict mode is turned off we attempt to violate the specification + * in the same way that most Http user agent's do (and many HTTP servers + * expect. + * + * NOTE: StrictMode is currently experimental and its functionlaity may change in the future. + * + */ + public void setStrictMode(boolean strictMode) + { + this.strictMode = strictMode; + } + + /** + * Returns the value of strictMode. + * + * NOTE: StrictMode is currently experimental and its functionlaity may change in the future. + * + * @return true if strict mode is enabled. + */ + public boolean isStrictMode() + { + return strictMode; + } + + /** * Set the specified request header, overwriting any * previous value. * Note that header-name matching is case-insensitive. @@ -423,7 +450,7 @@ Set visited = new HashSet(); Set realms = 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"))); @@ -431,28 +458,36 @@ log.debug("HttpMethodBase.execute(): looping."); } - if (!connection.isOpen()) { - if (log.isDebugEnabled()) { - log.debug("HttpMethodBase.execute(): opening connection."); + try{ + if (!connection.isOpen()) { + if (log.isDebugEnabled()) { + log.debug("HttpMethodBase.execute(): opening connection."); + } + connection.open(); } - connection.open(); - } - - writeRequest(state,connection); - used = true; - // need to close output?, but when? - - readResponse(state,connection); + writeRequest(state,connection); + used = true; + // need to close output?, but when? + readResponse(state,connection); + }catch(HttpRecoverableException e){ + if(retryCount >= maxRetries){ + throw new HttpException(e.toString()); + } + retryCount++; + connection.close(); + log.debug("HttpMethodBase.execute(): Caught recoverable exception, retrying..."); + continue; + } if (HttpStatus.SC_CONTINUE == statusCode) { if (!bodySent) { bodySent = writeRequestBody(state,connection); - readResponse(state,connection); } else { log.warn("HttpMethodBase.execute(): received 100 response, but I've already sent the response."); - break; + // According to RFC 2616 this respose should be ignored } + readResponse(state,connection); } if (!http11) { @@ -533,6 +568,19 @@ 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()); } @@ -749,16 +797,12 @@ } /** - * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s, - * if any, as long as no <tt>Cookie</tt> request header - * already exists. + * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s. */ protected void addCookieRequestHeader(HttpState state, HttpConnection conn) throws IOException, HttpException { - if (!requestHeaders.containsKey("cookie")) { - Header cookieHeader = Cookie.createCookieHeader(conn.getHost(), conn.getPort(), getPath(), conn.isSecure(), new Date(), state.getCookies()); - if (null != cookieHeader) { - setRequestHeader(cookieHeader); - } + Header cookieHeader = Cookie.createCookieHeader(conn.getHost(), conn.getPort(), getPath(), conn.isSecure(), new Date(), state.getCookies()); + if(null != cookieHeader) { + setRequestHeader(cookieHeader); } } @@ -912,7 +956,8 @@ statusLine = conn.readLine(); } if (statusLine == null) { - throw new HttpException("Error in parsing the status line from the response: unable to find line starting with \"HTTP/\""); + // A null statusLine means the connection was lost before we got a response. Try again. + throw new HttpRecoverableException("Error in parsing the status line from the response: unable to find line starting with \"HTTP/\""); } if ((!statusLine.startsWith("HTTP/1.1") && @@ -1078,11 +1123,37 @@ * @param conn the {@link HttpConnection} to read the response from */ protected void readResponseBody(HttpState state, HttpConnection conn) throws IOException, HttpException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + readResponseBody(state, conn, out); + + out.close(); + responseBody = out.toByteArray(); + } + + /** + * Read the response body from the given {@link HttpConnection}. + * <p> + * The current implementation simply consumes the expected + * response body (according to the values of the + * <tt>Content-Length</tt> and <tt>Transfer-Encoding</tt> + * headers, if any). + * <p> + * Subclasses may want to override this method to + * to customize the processing. + * + * @see #readResponse + * @see #processResponseBody + * + * @param state the client state + * @param conn the {@link HttpConnection} to read the response from + * @param out OutputStream to write the response body to + */ + protected void readResponseBody(HttpState state, HttpConnection conn, OutputStream out) throws IOException { if (log.isDebugEnabled()) { log.debug("HttpMethodBase.readResponseBody(HttpState,HttpConnection)"); } + responseBody = null; - ByteArrayOutputStream out = new ByteArrayOutputStream(); int expectedLength = 0; int foundLength = 0; { @@ -1098,6 +1169,15 @@ if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) { expectedLength = -1; } + } else if(canResponseHaveBody(statusCode)){ + /* + * According to the specification, a response with neither Content-Length + * nor Transfer-Encoding indicates that the response has no body. In + * the real world, this usually means that the server just didn't know + * the content-length when it sent the response. FIXME: Should we do + * this only in non-strict mode? + */ + expectedLength = -1; } } InputStream is = conn.getResponseInputStream(this); @@ -1125,8 +1205,6 @@ } } } - out.close(); - responseBody = out.toByteArray(); } /** @@ -1245,6 +1323,26 @@ } } + /** + * Per RFC 2616 section 4.3, some response can never contain + * a message body. + * + * @param status - the HTTP status code + * @return true if the message may contain a body, false if it can not contain a message body + */ + private static boolean canResponseHaveBody(int status) + { + boolean result = true; + + if((status >= 100 && status <= 199) || // 1XX + status == 204 || // NO CONTENT + status == 304){ // NOT MODIFIED + result = false; + } + + return result; + } + // ----------------------------------------------------- Instance Variables /** My request path. */ private String path = null; @@ -1268,6 +1366,10 @@ private boolean bodySent = false; /** The response body, assuming it has not be intercepted by a sub-class. */ private byte[] responseBody = null; + /** The maximum number of attempts to attempt recovery from an HttpRecoverableException. */ + private int maxRetries = 3; + /** True if we're in strict mode. */ + private boolean strictMode = true; // -------------------------------------------------------------- Constants
-- To unsubscribe, e-mail: <mailto:[EMAIL PROTECTED]> For additional commands, e-mail: <mailto:[EMAIL PROTECTED]>