commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From d...@apache.org
Subject cvs commit: jakarta-commons/httpclient/src/java/org/apache/commons/httpclient HttpMethodBase.java
Date Sun, 01 Sep 2002 14:28:15 GMT
dion        2002/09/01 07:28:15

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        HttpMethodBase.java
  Log:
  Reformatting and documentation
  
  Revision  Changes    Path
  1.51      +1407 -1137jakarta-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.50
  retrieving revision 1.51
  diff -u -r1.50 -r1.51
  --- HttpMethodBase.java	1 Sep 2002 02:31:55 -0000	1.50
  +++ HttpMethodBase.java	1 Sep 2002 14:28:15 -0000	1.51
  @@ -59,18 +59,13 @@
    * [Additional notices, if required by prior licensing conditions]
    *
    */
  -
   package org.apache.commons.httpclient;
   
  -import org.apache.commons.logging.Log;
  -import org.apache.commons.logging.LogFactory;
  -
   import java.io.ByteArrayInputStream;
   import java.io.ByteArrayOutputStream;
   import java.io.IOException;
   import java.io.InputStream;
   import java.io.OutputStream;
  -import java.net.MalformedURLException;
   import java.net.URL;
   import java.util.Date;
   import java.util.HashMap;
  @@ -79,34 +74,78 @@
   import java.util.Set;
   import java.util.StringTokenizer;
   
  +import org.apache.commons.logging.Log;
  +import org.apache.commons.logging.LogFactory;
  +
   /**
  - * <p>An abstract base implementation of {@link HttpMethod}.</p>
  - * <p>At minimum, subclasses will need to override</p>
  - * <ul><dl>
  - *  <dt>{@link #getName}</dt>
  - *  <dd>to return the approriate name for this method</dd>
  - * </dl></ul>
  - * <p>When a method's request may contain a body,
  - * subclasses will typically want to override:</p>
  - * <ul><dl>
  - *  <dt>{@link #getRequestContentLength}</dt>
  - *  <dd>to indicate the length (in bytes) of that body</dd>
  - *  <dt>{@link #writeRequestBody writeRequestBody(HttpState,HttpConnection)}</dt>
  - *  <dd>to write the body</dd>
  - * </dl></ul>
  - * <p>When a method requires additional request headers,
  - * subclasses will typically want to override:</p>
  - * <ul><dl>
  - *  <dt>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}</dt>
  - *  <dd>to write those headers</dd>
  - * </dl></ul>
  - * <p>When a method expects specific response headers,
  - * subclasses may want to override:</p>
  - * <ul><dl>
  - *  <dt>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}</dt>
  - *  <dd>to handle those headers</dd>
  - * </dl></ul>
  - *
  + * <p>
  + * An abstract base implementation of {@link HttpMethod}.
  + * </p>
  + * 
  + * <p>
  + * At minimum, subclasses will need to override
  + * </p>
  + * 
  + * <dl>
  + * <dt>
  + * {@link #getName}
  + * </dt>
  + * <dd>
  + * to return the approriate name for this method
  + * </dd>
  + * </dl>
  + * 
  + * <p>
  + * When a method's request may contain a body, subclasses will typically want
  + * to override:
  + * </p>
  + * 
  + * <dl>
  + * <dt>
  + * {@link #getRequestContentLength}
  + * </dt>
  + * <dd>
  + * to indicate the length (in bytes) of that body
  + * </dd>
  + * <dt>
  + * {@link #writeRequestBody writeRequestBody(HttpState,HttpConnection)}
  + * </dt>
  + * <dd>
  + * to write the body
  + * </dd>
  + * </dl>
  + * 
  + * <p>
  + * When a method requires additional request headers, subclasses will typically
  + * want to override:
  + * </p>
  + * 
  + * <dl>
  + * <dt>
  + * {@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
  + * </dt>
  + * <dd>
  + * to write those headers
  + * </dd>
  + * </dl>
  + * 
  + * <p>
  + * When a method expects specific response headers, subclasses may want to
  + * override:
  + * </p>
  + * 
  + * <dl>
  + * <dt>
  + * {@link #processResponseHeaders
  + * processResponseHeaders(HttpState,HttpConnection)}
  + * </dt>
  + * <dd>
  + * to handle those headers
  + * </dd>
  + * </dl>
  + * 
  + * 
  + * @version $Revision$ $Date$
    * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
    * @author Rodney Waldhoff
    * @author Sean C. Sullivan
  @@ -114,9 +153,76 @@
    * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
    * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
    * @author Ortwin GlЧck
  - * @version $Revision$ $Date$
    */
   public abstract class HttpMethodBase implements HttpMethod {
  +    //~ Static variables/initializers ииииииииииииииииииииииииииииииииииииииииии
  +
  +    /** Maximum number of redirects and authentications that will be followed */
  +    private static int maxForwards = 100;
  +    // -------------------------------------------------------------- Constants
  +
  +    /** Log object for this class. */
  +    private static final Log log = LogFactory.getLog(HttpMethod.class);
  +
  +    /** Log for any wire messages. */
  +    private static final Log wireLog = LogFactory.getLog("httpclient.wire");
  +
  +    /** The User-Agent header sent on every request. */
  +    protected static final Header USER_AGENT;
  +
  +    static {
  +        String agent = System.getProperties()
  +                             .getProperty("httpclient.useragent", 
  +                                          "Jakarta Commons-HttpClient/2.0M1");
  +        USER_AGENT = new Header("User-Agent", agent);
  +    }
  +
  +    //~ Instance variables иииииииииииииииииииииииииииииииииииииииииииииииииииии
  +
  +    /** My request headers, if any. */
  +    private HashMap requestHeaders = new HashMap();
  +
  +    /** My response headers, if any. */
  +    private HashMap responseHeaders = new HashMap();
  +    // ----------------------------------------------------- Instance Variables
  +
  +    /** My request path. */
  +    private String path = null;
  +
  +    /** My query string, if any. */
  +    private String queryString = null;
  +
  +    /** My response status text, if any. */
  +    private String statusText = null;
  +
  +    /** The response body, assuming it has not be intercepted by a sub-class. */
  +    private byte[] responseBody = null;
  +
  +    /** Whether or not the request body has been sent. */
  +    private boolean bodySent = false;
  +
  +    /** Whether or not I should automatically follow redirects. */
  +    private boolean followRedirects = false;
  +
  +    /** Whether or not I should use the HTTP/1.1 protocol. */
  +    private boolean http11 = true;
  +
  +    /** True if we're in strict mode. */
  +    private boolean strictMode = true;
  +
  +    /** Whether or not I have been executed. */
  +    private boolean used = false;
  +
  +    /**
  +     * The maximum number of attempts to attempt recovery from an
  +     * HttpRecoverableException.
  +     */
  +    private int maxRetries = 3;
  +
  +    /** My response status code, if any. */
  +    private int statusCode = -1;
  +
  +    //~ Constructors иииииииииииииииииииииииииииииииииииииииииииииииииииииииииии
   
       // ----------------------------------------------------------- Constructors
   
  @@ -128,200 +234,86 @@
   
       /**
        * Path-specifying constructor.
  -     *
  +     * 
        * @param path my path
        */
       public HttpMethodBase(String path) {
           setPath(path);
       }
   
  +    //~ Methods ииииииииииииииииииииииииииииииииииииииииииииииииииииииииииииииии
  +
       // ------------------------------------------- Property Setters and Getters
   
       /**
        * Obtain the name of this method, suitable for use in the "request line",
        * for example <tt>GET</tt> or <tt>POST</tt>.
  +     * 
        * @return the name of this method
        */
       public abstract String getName();
   
       /**
  -     * Set the path part of my request.
  -     * @param path the path to request
  -     */
  -    public void setPath(String path) {
  -        this.path = path;
  -    }
  -
  -    /**
  -     * Get the path part of my request.
  -     * @return the path to request or "/" if the path is blank.
  -     */
  -    public String getPath() {
  -        return (path == null || path.equals("")) ? "/" : path;
  -    }
  -
  -    /**
  -     * Get the HTTP version.
  -     * @return HTTP/1.1 if http11, HTTP/1.0 otherwise
  -     * @since 2.0
  -     */
  -    private String getHttpVersion() {
  -        return (http11 ? "HTTP/1.1" : "HTTP/1.0");
  -    }
  -
  -    /**
  -     * 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.
  -     * @param headerName the header's name
  -     * @param headerValue the header's value
  -     */
  -    public void setRequestHeader(String headerName, String headerValue) {
  -        Header header = new Header(headerName, headerValue);
  -        setRequestHeader(header);
  -    }
  -
  -    /**
  -     * Set the specified request header, overwriting any
  -     * previous value.
  -     * Note that header-name matching is case insensitive.
  -     * @param header the header
  +     * Set whether or not I should automatically follow HTTP redirects (status
  +     * code 302, etc.)
  +     * 
  +     * @param followRedirects true to follow redirects, false otherwise
        */
  -    public void setRequestHeader(Header header) {
  -        requestHeaders.put(header.getName().toLowerCase(), header);
  +    public void setFollowRedirects(boolean followRedirects) {
  +        this.followRedirects = followRedirects;
       }
   
       /**
  -     * Add the specified request header, NOT overwriting any
  -     * previous value.
  -     * Note that header-name matching is case insensitive.
  -     * @param headerName the header's name
  -     * @param headerValue the header's value
  +     * Whether or not I should automatically follow HTTP redirects (status code
  +     * 302, etc.)
  +     * 
  +     * @return <tt>true</tt> if I will automatically follow HTTP redirects
        */
  -    public void addRequestHeader(String headerName, String headerValue) {
  -        // "It must be possible to combine the multiple header fields into
  -        // one "field-name: field-value" pair, without changing the
  -        // semantics of the message, by appending each subsequent field-value
  -        // to the first, each separated by a comma."
  -        //   - HTTP/1.0 (4.3)
  -        Header header = getRequestHeader(headerName);
  -        if (null == header) {
  -            // header doesn't exist already, simply create with name and value
  -            header = new Header(headerName, headerValue);
  -        } else {
  -            // header exists, add this value to the comma separated list
  -            header.setValue(getNewHeaderValue(header, headerValue));
  -        }
  -        setRequestHeader(header);
  +    public boolean getFollowRedirects() {
  +        return this.followRedirects;
       }
   
       /**
  -     * "It must be possible to combine the multiple header fields into
  -     * one "field-name: field-value" pair, without changing the
  -     * semantics of the message, by appending each subsequent field-value
  -     * to the first, each separated by a comma."
  -     * //TODO: This method is trying to make up for deficiencies in Header.
  -     */
  -    private String getNewHeaderValue(Header existingHeader, String value) {
  -        String existingValue = existingHeader.getValue();
  -        if (existingValue == null) {
  -            existingValue = "";
  -        }
  -        String newValue = value;
  -        if (value == null) {
  -            newValue = "";
  -        }
  -        return existingValue + ", " + newValue;
  -    }
  -    
  -    /**
  -     * Add the specified request header.
  -     *
  -     * If a header of the same name already exists, the new value will be
  -     * appended onto the the existing value list. 
  -     * A <i>header</i> value of <code>null</code> will be ignored. 
  -     * Note that header-name matching is case insensitive.
  -     *
  -     * @param header the header to add to the request
  +     * Set whether or not I should use the HTTP/1.1 protocol. internal
  +     * 
  +     * @param http11 true to use HTTP/1.1, false to use 1.0
        */
  -    public void addRequestHeader(Header header) {
  -        log.trace("HttpMethodBase.addRequestHeader(Header)");
  -
  -        if (header == null) {
  -          log.debug("null header value ignored");
  -        } else {
  -            addRequestHeader(header.getName(), header.getValue());
  -        }
  +    public void setHttp11(boolean http11) {
  +        this.http11 = http11;
       }
   
  -    /**
  -     * Get the request header associated with the given name.
  -     * Header name matching is case insensitive.
  -     * <tt>null</tt> will be returned if either <i>headerName</i> is
  -     * <tt>null</tt> or there is no matching header for <i>headerName</i>.
  -     * @param headerName the header name to match
  -     * @return the matching header
  -     */
  -    public Header getRequestHeader(String headerName) {
  -        return (headerName == null) ? null :
  -            (Header)(requestHeaders.get(headerName.toLowerCase()));
  -    }
  +    // ---------------------------------------------- Protected Utility Methods
   
       /**
  -     * Remove the request header associated with the given name.
  -     * Note that header-name matching is case insensitive.
  -     * @param headerName the header name
  +     * Return <tt>true</tt> if I should use the HTTP/1.1 protocol. internal
  +     * 
  +     * @return <tt>true</tt> if I should use the HTTP/1.1 protocol
        */
  -    public void removeRequestHeader(String headerName) {
  -        requestHeaders.remove(headerName.toLowerCase());
  +    public boolean isHttp11() {
  +        return http11;
       }
   
       /**
  -     * Whether or not I should automatically follow
  -     * HTTP redirects (status code 302, etc.)
  -     * @return <tt>true</tt> if I will automatically follow HTTP redirects
  +     * Set the path part of my request.
  +     * 
  +     * @param path the path to request
        */
  -    public boolean getFollowRedirects() {
  -        return this.followRedirects;
  +    public void setPath(String path) {
  +        this.path = path;
       }
   
       /**
  -     * Set whether or not I should automatically follow
  -     * HTTP redirects (status code 302, etc.)
  +     * Get the path part of my request.
  +     * 
  +     * @return the path to request or "/" if the path is blank.
        */
  -    public void setFollowRedirects(boolean followRedirects) {
  -        this.followRedirects = followRedirects;
  +    public String getPath() {
  +        return (path == null || path.equals("")) ? "/" : path;
       }
   
       /**
        * Set my query string.
  +     * 
        * @param queryString the query string
        */
       public void setQueryString(String queryString) {
  @@ -330,22 +322,22 @@
   
       /**
        * Set my query string.
  -     * @param params an array of {@link NameValuePair}s
  -     *               to add as query string parameterss
  +     * 
  +     * @param params an array of {@link NameValuePair}s to add as query string
  +     *        parameterss
        */
       public void setQueryString(NameValuePair[] params) {
           log.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
           StringBuffer buf = new StringBuffer();
           boolean needAmp = false;
  -        for(int i = 0;i < params.length; i++) {
  +        for (int i = 0; i < params.length; i++) {
               if (params[i].getName() != null) {
                   if (needAmp) {
                       buf.append("&");
                   } else {
                       needAmp = true;
                   }
  -                buf.append(URIUtil.encode(params[i].getName()))
  -                    .append("=");
  +                buf.append(URIUtil.encode(params[i].getName())).append("=");
                   if (params[i].getValue() != null) {
                       buf.append(URIUtil.encode(params[i].getValue()));
                   }
  @@ -356,360 +348,231 @@
   
       /**
        * Get my query string.
  +     * 
  +     * @return The query string portion of the request
        */
       public String getQueryString() {
           return queryString;
       }
   
       /**
  -     * Return an array of my request headers.
  +     * Set the specified request header, overwriting any previous value. Note
  +     * that header-name matching is case-insensitive.
  +     * 
  +     * @param headerName the header's name
  +     * @param headerValue the header's value
        */
  -    public Header[] getRequestHeaders() {
  -        return (Header[])(requestHeaders.values().toArray(
  -            new Header[requestHeaders.size()]));
  +    public void setRequestHeader(String headerName, String headerValue) {
  +        Header header = new Header(headerName, headerValue);
  +        setRequestHeader(header);
       }
   
  -    // ---------------------------------------------------------------- Queries
  +    /**
  +     * Set the specified request header, overwriting any previous value. Note
  +     * that header-name matching is case insensitive.
  +     * 
  +     * @param header the header
  +     */
  +    public void setRequestHeader(Header header) {
  +        requestHeaders.put(header.getName().toLowerCase(), header);
  +    }
   
       /**
  -     * Confirm that I am ready to execute.
  -     * <p>
  -     * This implementation always returns <tt>true</tt>.
  -     * @return <tt>true</tt>
  +     * Get the request header associated with the given name. Header name
  +     * matching is case insensitive. <tt>null</tt> will be returned if either
  +     * <i>headerName</i> is <tt>null</tt> or there is no matching header for
  +     * <i>headerName</i>.
  +     * 
  +     * @param headerName the header name to match
  +     * 
  +     * @return the matching header
        */
  -    public boolean validate() {
  -        return true;
  +    public Header getRequestHeader(String headerName) {
  +        return (headerName == null)
  +               ? null : (Header) (requestHeaders.get(headerName.toLowerCase()));
       }
   
       /**
  -     * Return the status code associated with the latest response.
  +     * Provides access to the request headers.
  +     * 
  +     * @return an array of my request headers.
        */
  -    public int getStatusCode() {
  -        return statusCode;
  +    public Header[] getRequestHeaders() {
  +        return (Header[]) (requestHeaders.values().toArray(
  +            new Header[requestHeaders.size()]));
       }
   
       /**
  -     * Return the status text (or "reason phrase") associated with the latest
  -     * response.
  +     * Return my response body, if any, as a byte array. Otherwise return
  +     * <tt>null</tt>.
  +     * 
  +     * @return the response body
        */
  -    public String getStatusText() {
  -        return statusText;
  +    public byte[] getResponseBody() {
  +        return responseBody;
       }
   
       /**
  -     * Sets the specified response header.
  -     * @param header the header to set.
  -     * @since 2.0
  -     *
  +     * Return my response body, if any, as an {@link InputStream}. Otherwise
  +     * return <tt>null</tt>.
  +     * 
  +     * @return the response body as an {@link InputStream}
  +     * 
  +     * @throws IOException when there are errors obtaining the response
        */
  -    private void setResponseHeader(Header header){
  -        if (header == null) {
  -            return;
  -        }
  -        responseHeaders.put(header.getName().toLowerCase(), header);
  +    public InputStream getResponseBodyAsStream() throws IOException {
  +        return (null == responseBody)
  +               ? null : new ByteArrayInputStream(responseBody);
       }
   
       /**
  -     * Return an array my response headers.
  +     * Gets the response body as a string.
  +     * 
  +     * @return my response body, if any, as a {@link String}. Otherwise return
  +     *         <tt>null</tt>.
        */
  -    public Header[] getResponseHeaders() {
  -        return (Header[])(responseHeaders.values().toArray(
  -            new Header[responseHeaders.size()]));
  +    public String getResponseBodyAsString() {
  +        return (null == responseBody) ? null : new String(responseBody);
       }
   
       /**
  -     * Get the response header associated with the given name.
  -     * Header name matching is case insensitive.
  -     * <tt>null</tt> will be returned if either <i>headerName</i> is
  -     * <tt>null</tt> or there is no matching header for <i>headerName</i>.
  +     * Get the response header associated with the given name. Header name
  +     * matching is case insensitive. <tt>null</tt> will be returned if either
  +     * <i>headerName</i> is <tt>null</tt> or there is no matching header for
  +     * <i>headerName</i>.
  +     * 
        * @param headerName the header name to match
  +     * 
        * @return the matching header
        */
       public Header getResponseHeader(String headerName) {
  -        return (headerName == null) ? null :
  -            (Header)(responseHeaders.get(headerName.toLowerCase()));
  +        return (headerName == null)
  +               ? null 
  +               : (Header) (responseHeaders.get(headerName.toLowerCase()));
       }
   
       /**
  -     * Return my response body, if any,
  -     * as a byte array.
  -     * Otherwise return <tt>null</tt>.
  +     * Provide access to the response headers
  +     * 
  +     * @return an array of my response headers.
        */
  -    public byte[] getResponseBody() {
  -        return responseBody;
  +    public Header[] getResponseHeaders() {
  +        return (Header[]) (responseHeaders.values().toArray(
  +            new Header[responseHeaders.size()]));
       }
   
       /**
  -     * Return my response body, if any,
  -     * as a {@link String}.
  -     * Otherwise return <tt>null</tt>.
  +     * Provide access to the status code.
  +     * 
  +     * @return the status code associated with the latest response.
        */
  -    public String getResponseBodyAsString() {
  -        return null == responseBody ? null : new String(responseBody);
  +    public int getStatusCode() {
  +        return statusCode;
       }
   
       /**
  -     * Return my response body, if any,
  -     * as an {@link InputStream}.
  -     * Otherwise return <tt>null</tt>.
  +     * Provide access to the status text
  +     * 
  +     * @return the status text (or "reason phrase") associated with the latest
  +     *         response.
        */
  -    public InputStream getResponseBodyAsStream() throws IOException {
  -        return null == responseBody ? null : 
  -            new ByteArrayInputStream(responseBody);
  +    public String getStatusText() {
  +        return statusText;
       }
   
       /**
  -     * Return <tt>true</tt> if I have been {@link #execute executed}
  -     * but not recycled.
  +     * 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.
  +     * 
  +     * @param strictMode true for strict mode, false otherwise
        */
  -    public boolean hasBeenUsed() {
  -       return used;
  -    }
  -
  -    /**
  -     * Write a request and read the response.
  -     *
  -     * Both the write to the server will be retried {@link #maxRetries}
  -     * times if the operation fails with a HttpRecoverableException.
  -     * The write will only be attempted if the read has succeeded.
  -     * <p>
  -     * The <i>used</i> is set to true if the write succeeds.
  -     *
  -     * @param state the current state
  -     * @param connection the connection for communication
  -     * @throws HttpException when errors occur as part of the HTTP protocol
  -     *      conversation
  -     * @throws IOException when an I/O error occurs communicating with the 
  -     *      server
  -     * @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)");
  -
  -        //try to do the write
  -        int retryCount = 0;
  -        do {
  -            retryCount++;
  -            if (log.isTraceEnabled()){
  -                log.trace("Attempt number " + retryCount + " to write request");
  -            }
  -            try {
  -                if (!connection.isOpen()) {
  -                    log.debug("Opening the connection.");
  -                    connection.open();
  -                }
  -                writeRequest(state, connection);
  -                used = true; //write worked, mark this method as used
  -                break; //move onto the write
  -
  -            } catch (HttpRecoverableException httpre) {
  -                log.debug("Closing the connection.");
  -                connection.close();
  -                log.info("Recoverable exception caught when writing request");
  -                if (retryCount == maxRetries) {
  -                    log.warn("Attempt to write request has reached max retries: " +
  -                            maxRetries);
  -                    throw httpre;
  -                }
  -            }
  -        } while (retryCount <= maxRetries);
  -
  -        //try to do the read
  -        try {
  -            readResponse(state,connection);
  -        } catch (HttpRecoverableException httpre) {
  -            log.warn("Recoverable exception caught when reading response");
  -            log.debug("Closing the connection.");
  -            connection.close();
  -            throw httpre;
  -        }
  -
  -        //everything should be OK at this point
  +    public void setStrictMode(boolean strictMode) {
  +        this.strictMode = strictMode;
       }
   
  -    
       /**
  -     * On a {@link HttpStatus#SC_CONTINUE continue}, if there are more request
  -     * bytes to be sent, write them to the connection
  -     *
  -     * @param state the current state
  -     * @param connection the connection for communication
  -     * @throws HttpException when errors occur as part of the HTTP protocol
  -     *      conversation
  -     * @throws IOException when an I/O error occurs communicating with the 
  -     *      server
  +     * 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.
        */
  -    private void writeRemainingRequestBody(HttpState state, HttpConnection connection)
  -    throws HttpException, IOException {
  -        log.trace("enter writeRemainingRequestBody(HttpState, HttpConnection)");
  -
  -        if (HttpStatus.SC_CONTINUE == statusCode) {
  -            if (!bodySent) {
  -                bodySent = writeRequestBody(state, connection);
  -            } else {
  -                log.warn("Received status CONTINUE but he body has already been sent");
  -                // According to RFC 2616 this respose should be ignored
  -            }
  -            readResponse(state,connection);
  -        }
  +    public boolean isStrictMode() {
  +        return strictMode;
       }
   
       /**
  -     * Close the provided HTTP connection, if:
  -     *  http 1.0 and not using the 'connect' method, or
  -     *  http 1.1 and the Connection: close header is sent
  -     *
  -     * @param connection the HTTP connection to process
  +     * Add the specified request header, NOT overwriting any previous value.
  +     * Note that header-name matching is case insensitive.
  +     * 
  +     * @param headerName the header's name
  +     * @param headerValue the header's value
        */
  -    private void closeConnection(HttpConnection connection) {
  -        log.trace("enter closeConnection(HttpConnection)");
  -
  -        if (!http11) {
  -            if (getName().equals(ConnectMethod.NAME) && 
  -                    (statusCode == HttpStatus.SC_OK)) {
  -                log.debug("Leaving connection open for tunneling");
  -            } else {
  -                log.debug("Closing connection since using HTTP/1.0, " + 
  -                        "ConnectMethod and status is OK");
  -                connection.close();
  -            }
  +    public void addRequestHeader(String headerName, String headerValue) {
  +        // "It must be possible to combine the multiple header fields into
  +        // one "field-name: field-value" pair, without changing the
  +        // semantics of the message, by appending each subsequent field-value
  +        // to the first, each separated by a comma."
  +        //   - HTTP/1.0 (4.3)
  +        Header header = getRequestHeader(headerName);
  +        if (null == header) {
  +            // header doesn't exist already, simply create with name and value
  +            header = new Header(headerName, headerValue);
           } else {
  -            Header connectionHeader = getResponseHeader("connection");
  -            if (null != connectionHeader
  -                && "close".equalsIgnoreCase(connectionHeader.getValue())) {
  -                log.debug("Closing connection since \"Connection: close\" header found.");
  -                connection.close();
  -            }
  +            // header exists, add this value to the comma separated list
  +            header.setValue(getNewHeaderValue(header, headerValue));
           }
  +        setRequestHeader(header);
       }
   
  -    
       /**
  -     * process a response that requires authentication
  -     *
  -     * @param state the current state
  -     * @param connection the connection for communication
  -     * @return true if the request has completed process, false if more attempts
  -     *      are needed
  +     * Add the specified request header. If a header of the same name already
  +     * exists, the new value will be appended onto the the existing value
  +     * list.  A <i>header</i> value of <code>null</code> will be ignored. Note
  +     * that header-name matching is case insensitive.
  +     * 
  +     * @param header the header to add to the request
        */
  -    private boolean processAuthenticationResponse(HttpState state, 
  -    HttpConnection connection) {
  -        log.trace("enter  HttpMethodBase.processAuthenticationResponse(HttpState, HttpConnection)");
  -
  -        Set realms = new HashSet();
  -        Set proxyRealms = new HashSet();
  -
  -        // handle authentication required
  -        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;
  -        }
  -        boolean authenticated = false;
  -        // if there was a header requesting authentication
  -        if (null != wwwauth) {
  -            String pathAndCreds = getPath() + ":" + wwwauth.getValue();
  -            if (realmsUsed.contains(pathAndCreds)) {
  -                if (log.isInfoEnabled()) {
  -                    log.info("Already tried to authenticate to \"" 
  -                        + wwwauth.getValue() + "\" but still receiving "
  -                        + statusCode + ".");
  -                }
  -                return true;
  -            } else {
  -                realmsUsed.add(pathAndCreds);
  -            }
  -
  -            try {
  -                //remove preemptive header and reauthenticate
  -                switch (statusCode) {
  -                    case HttpStatus.SC_UNAUTHORIZED:
  -                        removeRequestHeader(Authenticator.WWW_AUTH_RESP);
  -                        authenticated = Authenticator.authenticate(this, state);
  -                        break;
  -                    case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
  -                        removeRequestHeader(Authenticator.PROXY_AUTH_RESP); 
  -                        authenticated = Authenticator.authenticateProxy(this,state);
  -                        break;
  -                }
  -            } catch (HttpException httpe) {
  -                log.warn(httpe.getMessage());
  -                return true; // finished request
  -            } catch (UnsupportedOperationException uoe) {
  -                log.warn(uoe.getMessage());
  -                //FIXME: should this return true?
  -            }
  +    public void addRequestHeader(Header header) {
  +        log.trace("HttpMethodBase.addRequestHeader(Header)");
   
  -            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
  -            }
  +        if (header == null) {
  +            log.debug("null header value ignored");
  +        } else {
  +            addRequestHeader(header.getName(), header.getValue());
           }
  -        
  -        return !authenticated; // finished processing if we aren't authenticated
  -    }
  -    
  -
  -    /** 
  -     * Generates a key used for idenifying visited URLs.
  -     */
  -    private String generateVisitedKey(HttpConnection conn){
  -        return conn.getHost() + ":" + conn.getPort() + "|" + 
  -            generateRequestLine(conn, getName(), getPath(), getQueryString(), getHttpVersion() );
       }
   
  -
       /**
  -     * Execute this method.
  -     *
  -     * Note that we cannot currently support redirects that change 
  -     * the connection 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 redirect response code, 
  -     * and allow the user agent to resubmit if desired.
  -     *
  -     * @param state {@link HttpState} information to associate with this 
  -     *      request. Must be non-null.
  -     * @param connection the {@link HttpConnection} to write to/read from.
  -     *      Must be non-null.
  -     *
  -     * Note that we cannot currently 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
  +     * Execute this method. Note that we cannot currently support redirects
  +     * that change  the connection 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 redirect response code,  and allow the user
        * agent to resubmit if desired.
  -     *
  -     * @throws IOException if an I/O error occurs
  -     * @throws HttpException  if an protocol exception occurs
  -     *
  +     * 
  +     * @param state {@link HttpState} information to associate with this
  +     *        request. Must be non-null.
  +     * @param conn the{@link HttpConnection} to write to/read from. Must be
  +     *        non-null. Note that we cannot currently 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.
  +     * 
        * @return the integer status code if one was obtained, or <tt>-1</tt>
  +     * 
  +     * @throws HttpException  if an protocol exception occurs
  +     * @throws IOException if an I/O error occurs
  +     * @throws NullPointerException if the state is null
        */
  -    public int execute(HttpState state, HttpConnection conn) 
  -    throws HttpException, IOException {
  +    public int execute(HttpState state, HttpConnection conn)
  +    throws HttpException, IOException, NullPointerException {
           log.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
  -        //TODO: This method is too large 
   
  +        //TODO: This method is too large 
           //check some error conditions
           if (null == state) {
               throw new NullPointerException("HttpState parameter");
  @@ -733,49 +596,50 @@
           Set visited = new HashSet();
           int forwardCount = 0; //protect from an infinite loop
   
  -        while(forwardCount++ < maxForwards) {
  -
  -            log.debug("Execute loop try " + forwardCount);
  +        while (forwardCount++ < maxForwards) {
  +            if (log.isDebugEnabled()) {
  +                log.debug("Execute loop try " + forwardCount);
  +            }
   
               //write the request and read the response, will retry
               processRequest(state, conn);
  -            
  +
               //if SC_CONTINUE write the request body
               writeRemainingRequestBody(state, conn);
   
               //close connection if required
               closeConnection(conn);
   
  -            switch(statusCode){
  +            switch (statusCode) {
                   case HttpStatus.SC_UNAUTHORIZED:
                   case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
                       // process authentication response
  -                    boolean authenticated = processAuthenticationResponse(state, conn);
  -                    if (authenticated){
  +                    if (processAuthenticationResponse(state, conn)) {
                           return statusCode;
                       }
                       break;
  -
                   case HttpStatus.SC_MOVED_TEMPORARILY:
                   case HttpStatus.SC_MOVED_PERMANENTLY:
                   case HttpStatus.SC_TEMPORARY_REDIRECT:
                       //TODO: This block should be factored into a new
                       //method called processRedirectResponse
  -                    if (!getFollowRedirects()){
  -                        log.info("Redirect requested but followRedirects is disabled");
  +                    if (!getFollowRedirects()) {
  +                        log.info("Redirect requested but followRedirects is "
  +                                 + "disabled");
                           return statusCode;
                       }
  -                    
                       //get the location header to find out where to redirect to
                       Header locationHeader = getResponseHeader("location");
                       if (locationHeader == null) {
                           // got a redirect response, but no location header
  -                        log.error("Received redirect response " + statusCode + " but no location header");
  +                        log.error("Received redirect response " + statusCode
  +                                  + " but no location header");
                           return statusCode;
                       }
                       String location = locationHeader.getValue();
  -                    if (log.isDebugEnabled()){
  -                        log.debug("Redirect requested to location '" + location + "'");
  +                    if (log.isDebugEnabled()) {
  +                        log.debug("Redirect requested to location '" + location
  +                                  + "'");
                       }
   
                       URL url; //the new url
  @@ -783,41 +647,46 @@
                       if (isStrictMode()) {
                           //rfc2616 demands the location value be a complete URI
                           //Location       = "Location" ":" absoluteURI
  -                        try{
  +                        try {
                               url = new URL(location);
                           } catch (Exception ex) {
  -                            log.warn("Redirected location '" + locationHeader.getValue() + 
  -                                    "' is not acceptable in strict mode");
  +                            log.warn("Redirected location '"
  +                                     + locationHeader.getValue()
  +                                     + "' is not acceptable in strict mode");
                               return statusCode;
                           }
                       } else { //not strict mode
                           //try to construct the new url based on the current url
                           try {
  -                            URL currentUrl = new URL(conn.getProtocol(), conn.getHost(), 
  -                                    conn.getPort(), getPath());
  +                            URL currentUrl = new URL(conn.getProtocol(), 
  +                                                     conn.getHost(), 
  +                                                     conn.getPort(), getPath());
                               url = new URL(currentUrl, location);
                           } catch (Exception ex) {
  -                            log.error("Redirected location '" + locationHeader.getValue() + "' is malformed");
  +                            log.error("Redirected location '"
  +                                      + locationHeader.getValue()
  +                                      + "' is malformed");
                               return statusCode;
                           }
                       }
   
  -                    
                       //check for redirect to a different protocol, host or port
                       String error = null;
  -                    if (! conn.getProtocol().equalsIgnoreCase(url.getProtocol())) {
  -                        error = "Redirect from protocol " + conn.getProtocol() + 
  -                                " to " + url.getProtocol() + " is not supported";
  +                    if (!conn.getProtocol().equalsIgnoreCase(
  +                        url.getProtocol())) {
  +                        error = "Redirect from protocol " + conn.getProtocol()
  +                                + " to " + url.getProtocol()
  +                                + " is not supported";
                       }
  -                    if (! conn.getHost().equalsIgnoreCase(url.getHost())) {
  -                        error = "Redirect from host " + conn.getHost() + 
  -                                " to " + url.getHost() + " is not supported";
  +                    if (!conn.getHost().equalsIgnoreCase(url.getHost())) {
  +                        error = "Redirect from host " + conn.getHost() + " to "
  +                                + url.getHost() + " is not supported";
                       }
                       if (conn.getPort() != url.getPort()) {
  -                        error = "Redirect from port " + conn.getPort() + 
  -                                " to " + url.getPort() + " is not supported";
  +                        error = "Redirect from port " + conn.getPort() + " to "
  +                                + url.getPort() + " is not supported";
                       }
  -                    if (error != null){
  +                    if (error != null) {
                           log.warn(error);
                           //throw new HttpException(error);
                           return statusCode;
  @@ -830,230 +699,264 @@
                       setQueryString(qs);
   
                       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.");
  +                        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.");
                       }
                       break;
  -
                   default:
                       // neither an unauthorized nor a redirect response
                       return statusCode;
  -
  -            }//end of switch
  +            } //end of switch
   
               //check to see if we have visited this url before
  -            if (visited.contains(generateVisitedKey(conn)) ) {
  +            if (visited.contains(generateVisitedKey(conn))) {
                   log.error("Link " + generateVisitedKey(conn) + "' revisited");
                   return statusCode;
               }
               visited.add(generateVisitedKey(conn));
  -
  -        }//end of loop
  +        } //end of loop
   
           log.error("Narrowly avoided an infinite loop in execute");
           return statusCode;
       }
   
  +    /**
  +     * Whether the object has been used and not recycled.
  +     * 
  +     * @return <tt>true</tt> if I have been {@link #execute executed} but not
  +     *         recycled.
  +     */
  +    public boolean hasBeenUsed() {
  +        return used;
  +    }
   
  +    /**
  +     * Recycle this method so that it can be used again. All of my instances
  +     * variables will be reset once this method has been called.
  +     */
  +    public void recycle() {
  +        log.trace("enter HttpMethodBase.recycle()");
   
  -
  -    // ------------------------------------------------------ Protected Methods
  +        path = null;
  +        followRedirects = false;
  +        queryString = null;
  +        requestHeaders.clear();
  +        responseHeaders.clear();
  +        statusCode = -1;
  +        statusText = null;
  +        used = false;
  +        http11 = true;
  +        bodySent = false;
  +        responseBody = null;
  +    }
   
       /**
  -     * <p>Writes my request to the given {@link HttpConnection}.</p>
  -     * <p>The request is written according to the following logic:</p>
  -     * <ol>
  -     * <li>
  -     *   {@link #writeRequestLine writeRequestLine(HttpState, HttpConnection)}
  -     *   is invoked to write the request line.
  -     * </li>
  -     * <li>
  -     *   {@link #writeRequestHeaders writeRequestHeaders(HttpState, HttpConnection)}
  -     *   is invoked to write the associated headers.</li>
  -     * <li>
  -     *   <tt>\r\n</tt> is sent to close the head part of the request.
  -     * </li>
  -     * <li>
  -     *  {@link #writeRequestBody writeRequestBody(HttpState, HttpConnection)}
  -     *  is invoked to write the body part of the request.
  -     * </li>
  -     * </ol>
  -     * <p>Subclasses may want to override one or more of the above methods to
  -     * to customize the processing. (Or they may choose to override this method
  -     * if dramatically different processing is required.)</p>
  -     *
  -     * @param state the client state
  -     * @param conn the {@link HttpConnection} to write the request to
  +     * Remove the request header associated with the given name. Note that
  +     * header-name matching is case insensitive.
  +     * 
  +     * @param headerName the header name
        */
  -    protected void writeRequest(HttpState state, HttpConnection conn)
  -    throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
  -        writeRequestLine(state,conn);
  -        writeRequestHeaders(state,conn);
  -        conn.writeLine(); // close head
  -        bodySent = writeRequestBody(state,conn);
  +    public void removeRequestHeader(String headerName) {
  +        requestHeaders.remove(headerName.toLowerCase());
       }
   
  +    // ---------------------------------------------------------------- Queries
   
       /**
  -     * Writes the "request line" to the given {@link HttpConnection}.
  +     * Confirm that I am ready to execute.
  +     * 
        * <p>
  -     * Subclasses may want to override this method to
  -     * to customize the processing.
  -     *
  -     * @see #generateRequestLine
  -     *
  -     * @param state the client state
  -     * @param conn the {@link HttpConnection} to write to
  +     * This implementation always returns <tt>true</tt>.
  +     * </p>
  +     * 
  +     * @return <tt>true</tt>
        */
  -    protected void writeRequestLine(HttpState state, HttpConnection conn)
  -    throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
  -        String requestLine = HttpMethodBase.generateRequestLine(conn, getName(),
  -            getPath(),getQueryString(), getHttpVersion());
  -        conn.print(requestLine);
  +    public boolean validate() {
  +        return true;
       }
   
       /**
  -     * Writes the request headers to the given {@link HttpConnection}.
  -     * <p>
  -     * This implementation invokes
  -     * {@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)},
  -     * and then writes each header to the request stream.
  -     * <p>
  -     * Subclasses may want to override this method to
  -     * to customize the processing.
  -     *
  -     * @see #addRequestHeaders
  -     * @see #getRequestHeaders
  -     *
  -     * @param state the client state
  -     * @param conn the {@link HttpConnection} to write to
  +     * Return the length (in bytes) of my request body, suitable for use in a
  +     * <tt>Content-Length</tt> header.
  +     * 
  +     * <p>
  +     * Return <tt>-1</tt> when the content-length is unknown.
  +     * </p>
  +     * 
  +     * <p>
  +     * This implementation returns <tt>0</tt>, indicating that the request has
  +     * no body.
  +     * </p>
  +     * 
  +     * @return <tt>0</tt>, indicating that the request has no body.
        */
  -    protected void writeRequestHeaders(HttpState state, HttpConnection conn)
  +    protected int getRequestContentLength() {
  +        return 0;
  +    }
  +
  +    /**
  +     * Adds an <tt>Authorization</tt> request if needed, as long as no
  +     * <tt>Authorization</tt> request header already exists.
  +     * 
  +     * @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
  +     */
  +    protected void addAuthorizationRequestHeader(HttpState state, 
  +                                                 HttpConnection conn)
       throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,HttpConnection)");
  -        addRequestHeaders(state,conn);
  -        Iterator it = requestHeaders.values().iterator();
  -        while(it.hasNext()) {
  -            conn.print(((Header)it.next()).toExternalForm());
  +        log.trace("enter HttpMethodBase.addAuthorizationRequestHeader("
  +                  + "HttpState, HttpConnection)");
  +
  +        // add authorization header, if needed
  +        if (getRequestHeader(Authenticator.WWW_AUTH_RESP) == null) {
  +            Header wwwAuthenticateHeader = getResponseHeader(
  +                                               Authenticator.WWW_AUTH);
  +            if (null != wwwAuthenticateHeader) {
  +                try {
  +                    Authenticator.authenticate(this, state);
  +                } catch (HttpException e) {
  +                    // ignored
  +                }
  +            }
           }
       }
   
       /**
  -     * Populates the request headers map to
  -     * with additional {@link Header headers} to be
  -     * submitted to the given {@link HttpConnection}.
  -     * <p>
  -     * This implementation adds <tt>User-Agent</tt>,
  -     * <tt>Host</tt>, <tt>Cookie</tt>, <tt>Content-Length</tt>,
  -     * <tt>Transfer-Encoding</tt>, and <tt>Authorization</tt>
  -     * headers, when appropriate.
  -     * <p>
  -     * Subclasses may want to override this method to
  -     * to add additional headers, and may choose to
  -     * invoke this implementation (via <tt>super</tt>)
  -     * to add the "standard" headers.
  -     *
  -     * @see #writeRequestHeaders
  -     *
  -     * @param state the client state
  -     * @param conn the {@link HttpConnection} the headers will eventually be written to
  +     * Adds a <tt>Content-Length</tt> or <tt>Transer-Encoding: Chunked</tt>
  +     * request header, as long as no <tt>Content-Length</tt> request header
  +     * already exists.
  +     * 
  +     * @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
        */
  -    protected void addRequestHeaders(HttpState state, HttpConnection conn)
  +    protected void addContentLengthRequestHeader(HttpState state, 
  +                                                 HttpConnection conn)
       throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addRequestHeaders(HttpState, HttpConnection)");
  +        log.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader("
  +                  + "HttpState, HttpConnection)");
   
  -        addUserAgentRequestHeader(state,conn);
  -        addHostRequestHeader(state,conn);
  -        addCookieRequestHeader(state,conn);
  -        addAuthorizationRequestHeader(state,conn);
  -        addProxyAuthorizationRequestHeader(state, conn);
  -        addContentLengthRequestHeader(state,conn);
  +        // add content length or chunking
  +        int len = getRequestContentLength();
  +        if (getRequestHeader("content-length") == null) {
  +            if (0 < len) {
  +                setRequestHeader("Content-Length", String.valueOf(len));
  +            } else if (http11 && (len < 0)) {
  +                setRequestHeader("Transfer-Encoding", "chunked");
  +            }
  +        }
       }
   
       /**
  -     * Adds a default <tt>User-Agent</tt> request header,
  -     * as long as no <tt>User-Agent</tt> request header
  -     * already exists.
  +     * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s.
  +     * 
  +     * @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
        */
  -    protected void addUserAgentRequestHeader(HttpState state, 
  -    HttpConnection conn) throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, HttpConnection)");
  +    protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
  +                  + "HttpConnection)");
   
  -        if (getRequestHeader("user-agent") == null) {
  -            setRequestHeader(HttpMethodBase.USER_AGENT);
  +        Header cookieHeader = Cookie.createCookieHeader(conn.getHost(), 
  +                                                        conn.getPort(), 
  +                                                        getPath(), 
  +                                                        conn.isSecure(), 
  +                                                        new Date(), 
  +                                                        state.getCookies());
  +        if (null != cookieHeader) {
  +            setRequestHeader(cookieHeader);
           }
       }
   
       /**
  -     * Adds a <tt>Host</tt> request header,
  -     * as long as no <tt>Host</tt> request header
  -     * already exists.
  +     * Adds a <tt>Host</tt> request header, as long as no <tt>Host</tt> request
  +     * header already exists.
  +     * 
  +     * @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
        */
  -    protected void addHostRequestHeader(HttpState state, HttpConnection conn) 
  +    protected void addHostRequestHeader(HttpState state, HttpConnection conn)
       throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, HttpConnection)");
  +        log.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
  +                  + "HttpConnection)");
  +
           // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based 
           // applications to send the Host request-header.
  -        // TODO: Add the ability to disable the sending of this header for HTTP/1.0 requests.
  -
  +        // TODO: Add the ability to disable the sending of this header for 
  +        //       HTTP/1.0 requests.
           String host = conn.getHost();
           int port = conn.getPort();
   
  -        if (getRequestHeader("host") != null){
  -            log.debug("Request to add Host header ignored: header already added");
  +        if (getRequestHeader("host") != null) {
  +            log.debug(
  +                "Request to add Host header ignored: header already added");
               return;
           }
  -        
  +
           if (isIpAddress(host)) {
               log.debug("Adding empty Host request header: host is an ipaddress");
               setRequestHeader("Host", "");
               return;
           }
  +        if (log.isDebugEnabled()) {
  +            log.debug("Adding Host request header");
  +        }
   
  -        log.debug("Adding Host request header");
           //appends the port only if not using the default port for the protocol
  -        if(conn.isSecure()) {
  -            setRequestHeader("Host", (port==443) ? host : host+':'+port);
  +        if (conn.isSecure()) {
  +            setRequestHeader("Host", (port == 443) ? host : host + ':' + port);
           } else {
  -            setRequestHeader("Host", (port==80) ? host : host+':'+port);
  -        }    
  -    }       
  -
  -    /**
  -     * Adds a <tt>Cookie</tt> request containing the matching {@link Cookie}s.
  -     */
  -    protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
  -    throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, HttpConnection)");
  -
  -        Header cookieHeader = Cookie.createCookieHeader(conn.getHost(), 
  -            conn.getPort(), getPath(), conn.isSecure(), new Date(), 
  -            state.getCookies());
  -        if (null != cookieHeader) {
  -            setRequestHeader(cookieHeader);
  +            setRequestHeader("Host", (port == 80) ? host : host + ':' + port);
           }
       }
   
       /**
  -     * Adds an <tt>Authorization</tt> request if needed,
  -     * as long as no <tt>Authorization</tt> request header
  -     * already exists.
  +     * Adds a <tt>Proxy-Authorization</tt> request if needed, as long as no
  +     * <tt>Proxy-Authorization</tt> request header already exists.
  +     * 
  +     * @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
        */
  -    protected void addAuthorizationRequestHeader(HttpState state, 
  -    HttpConnection conn) throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addAuthorizationRequestHeader(HttpState, HttpConnection)");
  +    protected void addProxyAuthorizationRequestHeader(HttpState state, 
  +                                                      HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader("
  +                  + "HttpState, HttpConnection)");
   
  -        // add authorization header, if needed
  -        if (getRequestHeader(Authenticator.WWW_AUTH_RESP) == null) {
  -            Header wwwAuthenticateHeader = getResponseHeader(Authenticator.WWW_AUTH);
  +        // add proxy authorization header, if needed
  +        if (getRequestHeader(Authenticator.PROXY_AUTH_RESP) == null) {
  +            Header wwwAuthenticateHeader = getResponseHeader(
  +                                               Authenticator.PROXY_AUTH);
               if (null != wwwAuthenticateHeader) {
                   try {
  -                    Authenticator.authenticate(this,state);
  -                } catch(HttpException e) {
  +                    Authenticator.authenticateProxy(this, state);
  +                } catch (HttpException e) {
                       // ignored
                   }
               }
  @@ -1061,411 +964,372 @@
       }
   
       /**
  -     * Adds a <tt>Proxy-Authorization</tt> request if needed,
  -     * as long as no <tt>Proxy-Authorization</tt> request header
  -     * already exists.
  +     * Populates the request headers map to with additional {@link Header
  +     * headers} to be submitted to the given {@link HttpConnection}.
  +     * 
  +     * <p>
  +     * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
  +     * <tt>Cookie</tt>, <tt>Content-Length</tt>, <tt>Transfer-Encoding</tt>,
  +     * and <tt>Authorization</tt> headers, when appropriate.
  +     * </p>
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to add additional
  +     * headers, and may choose to invoke this implementation (via
  +     * <tt>super</tt>) to add the "standard" headers.
  +     * </p>
  +     * 
  +     * @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 addProxyAuthorizationRequestHeader(HttpState state, 
  -    HttpConnection conn) throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader(HttpState, HttpConnection)");
  +    protected void addRequestHeaders(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
  +            + "HttpConnection)");
   
  -        // add proxy authorization header, if needed
  -        if (getRequestHeader(Authenticator.PROXY_AUTH_RESP) == null) {
  -            Header wwwAuthenticateHeader = getResponseHeader(Authenticator.PROXY_AUTH);
  -            if (null != wwwAuthenticateHeader) {
  -                try {
  -                    Authenticator.authenticateProxy(this,state);
  -                } catch(HttpException e) {
  -                    // ignored
  -                }
  -            }
  +        addUserAgentRequestHeader(state, conn);
  +        addHostRequestHeader(state, conn);
  +        addCookieRequestHeader(state, conn);
  +        addAuthorizationRequestHeader(state, conn);
  +        addProxyAuthorizationRequestHeader(state, conn);
  +        addContentLengthRequestHeader(state, conn);
  +    }
  +
  +    /**
  +     * Adds a default <tt>User-Agent</tt> request header, as long as no
  +     * <tt>User-Agent</tt> request header already exists.
  +     * 
  +     * @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
  +     */
  +    protected void addUserAgentRequestHeader(HttpState state, 
  +                                             HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
  +            + "HttpConnection)");
  +
  +        if (getRequestHeader("user-agent") == null) {
  +            setRequestHeader(HttpMethodBase.USER_AGENT);
           }
       }
   
       /**
  -     * Adds a <tt>Content-Length</tt> or
  -     * <tt>Transer-Encoding: Chunked</tt> request header,
  -     * as long as no <tt>Content-Length</tt> request header
  -     * already exists.
  +     * Throws an {@link IllegalStateException} if used but not recycled.
  +     * 
  +     * @throws IllegalStateException if the method has been used and not 
  +     *      recycled
        */
  -    protected void addContentLengthRequestHeader(HttpState state, 
  -    HttpConnection conn) throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader(HttpState, HttpConnection)");
  +    protected void checkNotUsed() throws IllegalStateException {
  +        if (used) {
  +            throw new IllegalStateException("Already used.");
  +        }
  +    }
   
  -        // add content length or chunking
  -        int len = getRequestContentLength();
  -        if (getRequestHeader("content-length") == null) {
  -            if (0 < len) {
  -                setRequestHeader("Content-Length",String.valueOf(len));
  -            } else if (http11 && (len < 0)) {
  -                setRequestHeader("Transfer-Encoding","chunked");
  +    /**
  +     * Throws an {@link IllegalStateException} if not used since last recycle.
  +     * 
  +     * @throws IllegalStateException if not used
  +     */
  +    protected void checkUsed()  throws IllegalStateException {
  +        if (!used) {
  +            throw new IllegalStateException("Not Used.");
  +        }
  +    }
  +
  +    // ------------------------------------------------- Static Utility Methods
  +
  +    /**
  +     * Generate an HTTP/S request line according to the specified attributes.
  +     * 
  +     * @param connection the connection the request will be sent to
  +     * @param name the method name generate a request for
  +     * @param reqPath the path for the request
  +     * @param qString the query string for the request
  +     * @param protocol the protocol to use (e.g. HTTP/1.0)
  +     * 
  +     * @return a line to send to the server that will fulfil the request
  +     */
  +    protected static String generateRequestLine(HttpConnection connection, 
  +        String name, String reqPath, 
  +        String qString, String protocol) {
  +        log.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
  +            + "String, String, String, String)");
  +
  +        StringBuffer buf = new StringBuffer();
  +        buf.append((null == reqPath)
  +                   ? "/" : URIUtil.encode(reqPath, URIUtil.pathSafe()));
  +        if (null != qString) {
  +            if (qString.indexOf("?") < 0) {
  +                buf.append("?");
  +            }
  +            buf.append(qString);
  +        }
  +
  +        if (!connection.isProxied() || connection.isTransparent()) {
  +            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");
  +            } else {
  +                return (name + " http://" + connection.getHost()
  +                       + ((80 == connection.getPort()
  +                               || -1 == connection.getPort())
  +                          ? "" : (":" + connection.getPort())) + buf.toString()
  +                       + " " + protocol + "\r\n");
               }
           }
       }
   
       /**
  -     * Return the length (in bytes) of
  -     * my request body, suitable for use in
  -     * a <tt>Content-Length</tt> header.
  +     * When this method is invoked, {@link #readResponseBody
  +     * readResponseBody(HttpState,HttpConnection)} will have been invoked.
  +     * 
        * <p>
  -     * Return <tt>-1</tt> when the content-length
  -     * is unknown.
  +     * This implementation does nothing.
  +     * </p>
  +     * 
        * <p>
  -     * This implementation returns <tt>0</tt>,
  -     * indicating that the request has no
  -     * body.
  -     * @return <tt>0</tt>, indicating that the request has no body.
  +     * Subclasses may want to override this method.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to read the response from
  +     * 
  +     * @see #readResponse
  +     * @see #readResponseBody
        */
  -    protected int getRequestContentLength() {
  -        return 0;
  +    protected void processResponseBody(HttpState state, HttpConnection conn) {
       }
   
       /**
  -     * Write the request body to the given {@link HttpConnection}
  +     * When this method is invoked, the response headers map will have been
  +     * populated with the response headers (in other words, {@link
  +     * #readResponseHeaders readResponseHeaders(HttpState,HttpConnection)}
  +     * will have been invoked).
  +     * 
        * <p>
  -     * If an expectation is required, this method should
  -     * ensure that it has been sent by checking the
  -     * {@link #getStatusCode status code}.
  -     * <p>
  -     * This method should return <tt>true</tt>
  -     * if the request body was actually sent (or is empty),
  -     * or <tt>false</tt> if it could not be sent for
  -     * some reason (for example, expectation required but
  -     * not present).
  +     * This implementation will handle the <tt>Set-Cookie</tt> and
  +     * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
  +     * the given {@link HttpState}.
  +     * </p>
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to specially process
  +     * additional headers, and/or invoke this method (via <tt>super</tt>) to
  +     * process the <tt>Set-Cookie</tt> and <tt>Set-Cookie2</tt> headers.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to read the response from
  +     * 
  +     * @see #readResponse
  +     * @see #readResponseHeaders
  +     */
  +    protected void processResponseHeaders(HttpState state, 
  +        HttpConnection conn) {
  +        log.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
  +            + "HttpConnection)");
  +
  +        // add cookies, if any
  +        // should we set cookies?
  +        Header setCookieHeader = getResponseHeader("set-cookie2");
  +        if (null == setCookieHeader) { //ignore old-style if new is supported
  +            setCookieHeader = getResponseHeader("set-cookie");
  +        }
  +
  +        if (setCookieHeader != null) {
  +            try {
  +                Cookie[] cookies = Cookie.parse(conn.getHost(), conn.getPort(), 
  +                                                getPath(), conn.isSecure(), 
  +                                                setCookieHeader);
  +                state.addCookies(cookies);
  +            } catch (Exception e) {
  +                // FIXME: Shouldn't be catching exception!
  +                log.error("Exception processing response headers", e);
  +            }
  +        }
  +    }
  +
  +    /**
  +     * When this method is invoked, the {@link #getStatusCode status code} and
  +     * {@link #getStatusText status text} values will have been set (in other
  +     * words, {@link #readStatusLine readStatusLine(HttpState,HttpConnection}
  +     * will have been invoked).
  +     * 
        * <p>
  -     * This implementation writes nothing and returns <tt>true</tt>.
  -     * @return <tt>true</tt>
  +     * Subclasses may want to override this method to respond to these value.
  +     * This implementation does nothing.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to read the response from
  +     * 
  +     * @see #readResponse
  +     * @see #readStatusLine
        */
  -    protected boolean writeRequestBody(HttpState state, HttpConnection conn)
  -    throws IOException, HttpException {
  -        return true;
  +    protected void processStatusLine(HttpState state, HttpConnection conn) {
       }
   
       /**
        * Reads the response from the given {@link HttpConnection}.
  +     * 
        * <p>
        * The response is written according to the following logic:
  +     * 
        * <ol>
        * <li>
  -     *   {@link #readStatusLine readStatusLine(HttpState,HttpConnection)}
  -     *   is invoked to read the request line.
  +     * {@link #readStatusLine readStatusLine(HttpState,HttpConnection)} is
  +     * invoked to read the request line.
        * </li>
        * <li>
  -     *   {@link #processStatusLine processStatusLine(HttpState,HttpConnection)}
  -     *   is invoked, allowing the method to respond to the status line if desired.
  +     * {@link #processStatusLine processStatusLine(HttpState,HttpConnection)}
  +     * is invoked, allowing the method to respond to the status line if
  +     * desired.
        * </li>
        * <li>
  -     *   {@link #readResponseHeaders readResponseHeaders(HttpState,HttpConnection}
  -     *   is invoked to read the associated headers.
  +     * {@link #readResponseHeaders
  +     * readResponseHeaders(HttpState,HttpConnection} is invoked to read the
  +     * associated headers.
        * </li>
        * <li>
  -     *   {@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection}
  -     *   is invoked, allowing the method to respond to the headers if desired.
  +     * {@link #processResponseHeaders
  +     * processResponseHeaders(HttpState,HttpConnection} is invoked, allowing
  +     * the method to respond to the headers if desired.
        * </li>
        * <li>
  -     *   {@link #readResponseBody readResponseBody(HttpState,HttpConnection)}
  -     *   is invoked to read the associated body (if any).
  +     * {@link #readResponseBody readResponseBody(HttpState,HttpConnection)} is
  +     * invoked to read the associated body (if any).
        * </li>
        * <li>
  -     *   {@link #processResponseBody processResponseBody(HttpState,HttpConnection}
  -     *   is invoked, allowing the method to respond to the body if desired.
  +     * {@link #processResponseBody
  +     * processResponseBody(HttpState,HttpConnection} is invoked, allowing the
  +     * method to respond to the body if desired.
        * </li>
        * </ol>
  -     * Subclasses may want to override one or more of the above methods to
  -     * to customize the processing. (Or they may choose to override this method
  +     * 
  +     * Subclasses may want to override one or more of the above methods to to
  +     * customize the processing. (Or they may choose to override this method
        * if dramatically different processing is required.)
  -     *
  +     * </p>
  +     * 
        * @param state the client state
        * @param conn the {@link HttpConnection} to read the response from
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
        */
       protected void readResponse(HttpState state, HttpConnection conn)
       throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
  -        readStatusLine(state,conn);
  -        processStatusLine(state,conn);
  -        readResponseHeaders(state,conn);
  -        processResponseHeaders(state,conn);
  -        readResponseBody(state,conn);
  -        processResponseBody(state,conn);
  +        log.trace(
  +            "enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
  +        readStatusLine(state, conn);
  +        processStatusLine(state, conn);
  +        readResponseHeaders(state, conn);
  +        processResponseHeaders(state, conn);
  +        readResponseBody(state, conn);
  +        processResponseBody(state, conn);
       }
   
       /**
  -     * Read the status line from the given {@link HttpConnection},
  -     * setting my {@link #getStatusCode status code} and
  -     * {@link #getStatusText status text}.
  +     * Read the response body from the given {@link HttpConnection}.
  +     * 
        * <p>
  -     * Subclasses may want to override this method to
  -     * to customize the processing.
  -     *
  -     * @see #readResponse
  -     * @see #processStatusLine
  -     *
  +     * 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>
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to customize the
  +     * processing.
  +     * </p>
  +     * 
        * @param state the client state
        * @param conn the {@link HttpConnection} to read the response from
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
  +     * 
  +     * @see #readResponse
  +     * @see #processResponseBody
        */
  -    protected void readStatusLine(HttpState state, HttpConnection conn) 
  +    protected void readResponseBody(HttpState state, HttpConnection conn)
       throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
  -
  -        statusCode = -1;
  -        statusText = null;
  -
  -        String statusLine = conn.readLine();
  +        log.trace(
  +            "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
   
  -        while(statusLine != null && !statusLine.startsWith("HTTP/")) {
  -            statusLine = conn.readLine();
  -        }
  -        if (statusLine == null) {
  -            // 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") &&
  -            !statusLine.startsWith("HTTP/1.0"))) {
  -            throw new HttpException("Unrecognized server protocol :" + statusLine);
  -        }
  -
  -        http11 = statusLine.startsWith("HTTP/1.1");
  -
  -        int at = statusLine.indexOf(" ");
  -        if (at < 0) {
  -            throw new HttpException("Unable to parse the status line: " + statusLine);
  -        }
  -
  -        int to = statusLine.indexOf(" ", at + 1);
  -        if (to < 0) {
  -            to = statusLine.length();
  -        }
  -
  -        try {
  -            statusCode = Integer.parseInt(statusLine.substring(at + 1, to));
  -        } catch (NumberFormatException e) {
  -            throw new HttpException("Unable to parse status code from status line: " + statusLine);
  -        }
  +        ByteArrayOutputStream out = new ByteArrayOutputStream();
  +        readResponseBody(state, conn, out);
   
  -        try {
  -            if (to < statusLine.length()) {
  -                statusText = statusLine.substring(to + 1);
  -            }
  -        } catch (StringIndexOutOfBoundsException e) {
  -            throw new HttpException("Status text not specified: " + statusLine);
  -        }
  +        out.close();
  +        responseBody = out.toByteArray();
       }
   
       /**
  -     * When this method is invoked, the {@link #getStatusCode status code}
  -     * and {@link #getStatusText status text} values will have been set (in other
  -     * words, {@link #readStatusLine readStatusLine(HttpState,HttpConnection} will
  -     * have been invoked).
  +     * Read the response body from the given {@link HttpConnection}.
  +     * 
        * <p>
  -     * Subclasses may want to override this method to respond to these value.
  -     * This implementation does nothing.
  -     *
  -     * @see #readResponse
  -     * @see #readStatusLine
  -     *
  +     * 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>
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to customize the
  +     * processing.
  +     * </p>
  +     * 
        * @param state the client state
        * @param conn the {@link HttpConnection} to read the response from
  -     */
  -    protected void processStatusLine(HttpState state, HttpConnection conn) {
  -    }
  -
  -    /**
  -     * Read response headers from the given {@link HttpConnection},
  -     * populating the response headers map.
  -     * <p>
  -     * Subclasses may want to override this method to
  -     * to customize the processing.
  -     * <p>
  -     * "It must be possible to combine the multiple header fields into
  -     * one "field-name: field-value" pair, without changing the
  -     * semantics of the message, by appending each subsequent field-value
  -     * to the first, each separated by a comma."
  -     *   - HTTP/1.0 (4.3)
  +     * @param out OutputStream to write the response body to
  +     * @throws IOException when i/o errors occur reading the response
        *
        * @see #readResponse
  -     * @see #processResponseHeaders
  -     *
  -     * @param state the client state
  -     * @param conn the {@link HttpConnection} to read the response from
  +     * @see #processResponseBody
        */
  -    protected void readResponseHeaders(HttpState state, HttpConnection conn)
  -    throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.readResponseHeaders(HttpState,HttpConnection)");
  -
  -        responseHeaders.clear();
  -
  -        String name = null;
  -        String value = null;
  -        for(;;) {
  -            String line = conn.readLine();
  -            if ((line == null) || (line.length() < 1)) {
  -                break;
  -            }
  -
  -            // Parse the header name and value
  -            // Check for folded headers first
  -            // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
  -            // discussion on folded headers
  -
  -             boolean isFolded = false;
  -             if ( line.charAt(0) == ' ' || line.charAt(0) == '\t' )
  -             {
  -                 // we have continuation folded header
  -                 // so append value
  -                 isFolded = true;
  -                 value = line.substring(1).trim();
  -             }
  -             else
  -             {
  -                 // Otherwise we should have normal HTTP header line
  -                 // Parse the header name and value
  -                 int colon = line.indexOf(":");
  -                 if (colon < 0) {
  -                     throw new HttpException("Unable to parse header: " + line);
  -                 }
  -                 name = line.substring(0, colon).trim();
  -                 value = line.substring(colon + 1).trim();
  -             }
  -             Header header = getResponseHeader(name);
  -             if (null == header) {
  -                 header = new Header(name, value);
  -             } else {
  -                 String oldvalue =  header.getValue();
  -                 if (null != oldvalue) {
  -                     if ( isFolded ) {
  -                         // LWS becomes space plus extended value
  -                         header = new Header(name,oldvalue + " " + value);
  -                     }
  -                     else {
  -                         // Append additional header value
  -                         header = new Header(name,oldvalue + ", " + value);
  -                     }
  -                 } else {
  -                     header = new Header(name,value);
  -                 }
  -             }
  -             setResponseHeader(header);
  -        }
  -    }
  -
  -    /**
  -     * When this method is invoked, the response headers
  -     * map will have been populated with the response headers
  -     * (in other words,
  -     * {@link #readResponseHeaders readResponseHeaders(HttpState,HttpConnection)}
  -     * will have been invoked).
  -     * <p>
  -     * This implementation will handle the <tt>Set-Cookie</tt>
  -     * and <tt>Set-Cookie2</tt> headers, if any, adding the
  -     * relevant cookies to the given {@link HttpState}.
  -     * <p>
  -     * Subclasses may want to override this method to
  -     * specially process additional headers, and/or
  -     * invoke this method (via <tt>super</tt>) to process
  -     * the <tt>Set-Cookie</tt> and <tt>Set-Cookie2</tt> headers.
  -     *
  -     * @see #readResponse
  -     * @see #readResponseHeaders
  -     *
  -     * @param state the client state
  -     * @param conn the {@link HttpConnection} to read the response from
  -     */
  -    protected void processResponseHeaders(HttpState state, HttpConnection conn) 
  -    {
  -        log.trace("enter HttpMethodBase.processResponseHeaders(HttpState, HttpConnection)");
  -        // add cookies, if any
  -        // should we set cookies?
  -        Header setCookieHeader = getResponseHeader("set-cookie2");
  -        if (null == setCookieHeader) { //ignore old-style if new is supported
  -            setCookieHeader = getResponseHeader("set-cookie");
  -        }
  -
  -        if (setCookieHeader != null) {
  -            try {
  -                Cookie[] cookies = Cookie.parse(conn.getHost(), conn.getPort(), getPath(), conn.isSecure(), setCookieHeader);
  -                state.addCookies(cookies);
  -            } catch (Exception e) {
  -                // FIXME: Shouldn't be catching exception!
  -                log.error("Exception processing response headers", e);
  -            }
  -        }
  -    }
  -
  -
  -    /**
  -     * 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
  -     */
  -    protected void readResponseBody(HttpState state, HttpConnection conn)
  -    throws IOException, HttpException {
  -        log.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
  -
  -        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 {
  -        log.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
  +    protected void readResponseBody(HttpState state, HttpConnection conn, 
  +                                    OutputStream out)
  +    throws IOException {
  +        log.trace(
  +            "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
   
           responseBody = null;
           int expectedLength = 0;
           int foundLength = 0;
           {
               Header lengthHeader = getResponseHeader("Content-Length");
  -            Header transferEncodingHeader = getResponseHeader("Transfer-Encoding");
  +            Header transferEncodingHeader = getResponseHeader(
  +                                                "Transfer-Encoding");
               if (null != lengthHeader) {
                   try {
                       expectedLength = Integer.parseInt(lengthHeader.getValue());
  -                } catch(NumberFormatException e) {
  +                } catch (NumberFormatException e) {
                       // ignored
                   }
               } else if (null != transferEncodingHeader) {
  -                if ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
  +                if ("chunked".equalsIgnoreCase(
  +                        transferEncodingHeader.getValue())) {
                       expectedLength = -1;
                   }
  -            } else if(canResponseHaveBody(statusCode) && !getName().equals(ConnectMethod.NAME)){
  +            } else if (canResponseHaveBody(statusCode)
  +                       && !getName().equals(ConnectMethod.NAME)) {
                   /*
  -                 * 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?
  +                 * 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?
                    * If we do this we will hang forever waiting for a body!
                    */
                   expectedLength = -1;
  @@ -1474,7 +1338,7 @@
           InputStream is = conn.getResponseInputStream(this);
           byte[] buffer = new byte[4096];
           int nb = 0;
  -        while(expectedLength == -1 || foundLength < expectedLength) {
  +        while ((expectedLength == -1) || (foundLength < expectedLength)) {
               nb = is.read(buffer);
               if (nb == -1) {
                   break;
  @@ -1491,7 +1355,10 @@
                   if (foundLength == expectedLength) {
                       break;
                   } else if (foundLength > expectedLength) {
  -                    log.warn("HttpMethodBase.readResponseBody(): expected length (" + expectedLength + ") exceeded.  Found " + foundLength + " bytes.");
  +                    log.warn(
  +                        "HttpMethodBase.readResponseBody(): expected length ("
  +                        + expectedLength + ") exceeded.  Found " + foundLength
  +                        + " bytes.");
                       break;
                   }
               }
  @@ -1499,153 +1366,320 @@
       }
   
       /**
  -     * When this method is invoked,
  -     * {@link #readResponseBody readResponseBody(HttpState,HttpConnection)}
  -     * will have been invoked.
  -     * <p>
  -     * This implementation does nothing.
  -     * <p>
  -     * Subclasses may want to override this method.
  -     *
  -     * @see #readResponse
  -     * @see #readResponseBody
  -     *
  +     * Read response headers from the given {@link HttpConnection}, populating
  +     * the response headers map.
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to customize the
  +     * processing.
  +     * </p>
  +     * 
  +     * <p>
  +     * "It must be possible to combine the multiple header fields into one
  +     * "field-name: field-value" pair, without changing the semantics of the
  +     * message, by appending each subsequent field-value to the first, each
  +     * separated by a comma." - HTTP/1.0 (4.3)
  +     * </p>
  +     * 
        * @param state the client state
        * @param conn the {@link HttpConnection} to read the response from
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
  +     * 
  +     * @see #readResponse
  +     * @see #processResponseHeaders
        */
  -    protected void processResponseBody(HttpState state, HttpConnection conn) {
  +    protected void readResponseHeaders(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
  +            + "HttpConnection)");
  +
  +        responseHeaders.clear();
  +
  +        String name = null;
  +        String value = null;
  +        for (; ;) {
  +            String line = conn.readLine();
  +            if ((line == null) || (line.length() < 1)) {
  +                break;
  +            }
  +
  +            // Parse the header name and value
  +            // Check for folded headers first
  +            // Detect LWS-char see HTTP/1.0 or HTTP/1.1 Section 2.2
  +            // discussion on folded headers
  +            boolean isFolded = false;
  +            if ((line.charAt(0) == ' ') || (line.charAt(0) == '\t')) {
  +                // we have continuation folded header
  +                // so append value
  +                isFolded = true;
  +                value = line.substring(1).trim();
  +            } else {
  +                // Otherwise we should have normal HTTP header line
  +                // Parse the header name and value
  +                int colon = line.indexOf(":");
  +                if (colon < 0) {
  +                    throw new HttpException("Unable to parse header: " + line);
  +                }
  +                name = line.substring(0, colon).trim();
  +                value = line.substring(colon + 1).trim();
  +            }
  +            Header header = getResponseHeader(name);
  +            if (null == header) {
  +                header = new Header(name, value);
  +            } else {
  +                String oldvalue = header.getValue();
  +                if (null != oldvalue) {
  +                    if (isFolded) {
  +                        // LWS becomes space plus extended value
  +                        header = new Header(name, oldvalue + " " + value);
  +                    } else {
  +                        // Append additional header value
  +                        header = new Header(name, oldvalue + ", " + value);
  +                    }
  +                } else {
  +                    header = new Header(name, value);
  +                }
  +            }
  +            setResponseHeader(header);
  +        }
       }
   
       /**
  -     * Recycle this method so that it can be used again.
  -     * All of my instances variables will be reset
  -     * once this method has been called.
  +     * Read the status line from the given {@link HttpConnection}, setting my
  +     * {@link #getStatusCode status code} and {@link #getStatusText status
  +     * text}.
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to customize the
  +     * processing.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to read the response from
  +     * 
  +     * @throws IOException when errors occur reading the status line
  +     * @throws HttpException If there is no status line, the protocol is not
  +     *      recognised, if we are unable to parse the status code from the line,
  +     *      or there was no status text
  +     * @throws HttpRecoverableException when the status line is null and the
  +     *      request should be retried
  +     * 
  +     * @see #readResponse
  +     * @see #processStatusLine
        */
  -    public void recycle() {
  -        log.trace("enter HttpMethodBase.recycle()");
  +    protected void readStatusLine(HttpState state, HttpConnection conn)
  +    throws IOException, HttpRecoverableException, HttpException {
  +        log.trace(
  +            "enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
   
  -        path = null;
  -        followRedirects = false;
  -        queryString = null;
  -        requestHeaders.clear();
  -        responseHeaders.clear();
           statusCode = -1;
           statusText = null;
  -        used = false;
  -        http11 = true;
  -        bodySent = false;
  -        responseBody = null;
  -    }
   
  -    // ---------------------------------------------- Protected Utility Methods
  +        String statusLine = conn.readLine();
   
  -    /**
  -     * Return <tt>true</tt> if I should use the HTTP/1.1 protocol.
  -     * @internal
  -     */
  -    public boolean isHttp11() {
  -        return http11;
  +        while ((statusLine != null) && !statusLine.startsWith("HTTP/")) {
  +            statusLine = conn.readLine();
  +        }
  +        if (statusLine == null) {
  +            // 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")
  +                && !statusLine.startsWith("HTTP/1.0"))) {
  +            throw new HttpException("Unrecognized server protocol :"
  +                                    + statusLine);
  +        }
  +
  +        http11 = statusLine.startsWith("HTTP/1.1");
  +
  +        int at = statusLine.indexOf(" ");
  +        if (at < 0) {
  +            throw new HttpException("Unable to parse the status line: "
  +                                    + statusLine);
  +        }
  +
  +        int to = statusLine.indexOf(" ", at + 1);
  +        if (to < 0) {
  +            to = statusLine.length();
  +        }
  +
  +        try {
  +            statusCode = Integer.parseInt(statusLine.substring(at + 1, to));
  +        } catch (NumberFormatException e) {
  +            throw new HttpException(
  +                "Unable to parse status code from status line: " + statusLine);
  +        }
  +
  +        try {
  +            if (to < statusLine.length()) {
  +                statusText = statusLine.substring(to + 1);
  +            }
  +        } catch (StringIndexOutOfBoundsException e) {
  +            throw new HttpException("Status text not specified: " + statusLine);
  +        }
       }
   
  +    // ------------------------------------------------------ Protected Methods
  +
       /**
  -     * Set whether or not I should use the HTTP/1.1 protocol.
  -     * @internal
  +     * <p>
  +     * Writes my request to the given {@link HttpConnection}.
  +     * </p>
  +     * 
  +     * <p>
  +     * The request is written according to the following logic:
  +     * </p>
  +     * 
  +     * <ol>
  +     * <li>
  +     * {@link #writeRequestLine writeRequestLine(HttpState, HttpConnection)} is
  +     * invoked to write the request line.
  +     * </li>
  +     * <li>
  +     * {@link #writeRequestHeaders writeRequestHeaders(HttpState,
  +     * HttpConnection)} is invoked to write the associated headers.
  +     * </li>
  +     * <li>
  +     * <tt>\r\n</tt> is sent to close the head part of the request.
  +     * </li>
  +     * <li>
  +     * {@link #writeRequestBody writeRequestBody(HttpState, HttpConnection)} is
  +     * invoked to write the body part of the request.
  +     * </li>
  +     * </ol>
  +     * 
  +     * <p>
  +     * Subclasses may want to override one or more of the above methods to to
  +     * customize the processing. (Or they may choose to override this method
  +     * if dramatically different processing is required.)
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to write the request to
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
        */
  -    public void setHttp11(boolean http11) {
  -        this.http11 = http11;
  +    protected void writeRequest(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace(
  +            "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
  +        writeRequestLine(state, conn);
  +        writeRequestHeaders(state, conn);
  +        conn.writeLine(); // close head
  +        bodySent = writeRequestBody(state, conn);
       }
   
       /**
  -     * Throws an {@link IllegalStateException} if
  -     * used by not recycled.
  +     * Write the request body to the given {@link HttpConnection}
  +     * 
  +     * <p>
  +     * If an expectation is required, this method should ensure that it has
  +     * been sent by checking the {@link #getStatusCode status code}.
  +     * </p>
  +     * 
  +     * <p>
  +     * This method should return <tt>true</tt> if the request body was actually
  +     * sent (or is empty), or <tt>false</tt> if it could not be sent for some
  +     * reason (for example, expectation required but not present).
  +     * </p>
  +     * 
  +     * <p>
  +     * This implementation writes nothing and returns <tt>true</tt>.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the connection to write to
  +     * 
  +     * @return <tt>true</tt>
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
        */
  -    protected void checkNotUsed() {
  -        if (used) {
  -            throw new IllegalStateException("Already used.");
  -        }
  +    protected boolean writeRequestBody(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        return true;
       }
   
       /**
  -     * Throws an {@link IllegalStateException} if
  -     * not used since last recycle.
  +     * Writes the request headers to the given {@link HttpConnection}.
  +     * 
  +     * <p>
  +     * This implementation invokes {@link #addRequestHeaders
  +     * addRequestHeaders(HttpState,HttpConnection)}, and then writes each
  +     * header to the request stream.
  +     * </p>
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to customize the
  +     * processing.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to write to
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
  +     * 
  +     * @see #addRequestHeaders
  +     * @see #getRequestHeaders
        */
  -    protected void checkUsed() {
  -        if (!used) {
  -            throw new IllegalStateException("Not Used.");
  +    protected void writeRequestHeaders(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
  +            + "HttpConnection)");
  +        addRequestHeaders(state, conn);
  +        Iterator it = requestHeaders.values().iterator();
  +        while (it.hasNext()) {
  +            conn.print(((Header) it.next()).toExternalForm());
           }
       }
   
  -    // ------------------------------------------------- Static Utility Methods
  -
       /**
  -     * Generate an HTTP/S request line according to
  -     * the specified attributes.
  +     * Writes the "request line" to the given {@link HttpConnection}.
  +     * 
  +     * <p>
  +     * Subclasses may want to override this method to to customize the
  +     * processing.
  +     * </p>
  +     * 
  +     * @param state the client state
  +     * @param conn the {@link HttpConnection} to write to
  +     * @throws IOException when i/o errors occur reading the response
  +     * @throws HttpException when a protocol error occurs or state is invalid
  +     * 
  +     * @see #generateRequestLine
        */
  -    protected static String generateRequestLine(HttpConnection connection,
  -    String name, String reqPath, String qString, String protocol) {
  -        log.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, String, String, String, String)");
  -
  -        StringBuffer buf = new StringBuffer();
  -        buf.append(null == reqPath ? "/" : URIUtil.encode(reqPath,URIUtil.pathSafe()));
  -        if (null != qString) {
  -            if (qString.indexOf("?") < 0) {
  -                buf.append("?");
  -            }
  -            buf.append(qString);
  -        }
  -
  -        if (!connection.isProxied() || connection.isTransparent()) {
  -            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");
  -            } else {
  -                return (name +
  -                       " http://" +
  -                       connection.getHost() +
  -                       ((80 == connection.getPort() || -1 == connection.getPort()) ? "" : (":" + connection.getPort()) ) +
  -                       buf.toString() +
  -                       " " +
  -                       protocol +
  -                       "\r\n");
  -            }
  -        }
  +    protected void writeRequestLine(HttpState state, HttpConnection conn)
  +    throws IOException, HttpException {
  +        log.trace(
  +            "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
  +        String requestLine = 
  +            HttpMethodBase.generateRequestLine(conn, getName(),
  +                                               getPath(), getQueryString(), 
  +                                               getHttpVersion());
  +        conn.print(requestLine);
       }
   
       /**
  -     * 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
  +     * Get the HTTP version.
  +     * 
  +     * @return HTTP/1.1 if http11, HTTP/1.0 otherwise
  +     * 
  +     * @since 2.0
        */
  -    private static boolean canResponseHaveBody(int status) {
  -        log.trace("enter HttpMethodBase.canResponseHaveBody(int)");
  -
  -        boolean result = true;
  -
  -        if((status >= 100 && status <= 199) ||    // 1XX
  -           status == 204 ||                       // NO CONTENT
  -           status == 304){                        // NOT MODIFIED
  -            result = false;
  -        }
  -
  -        return result;
  +    private String getHttpVersion() {
  +        return (http11 ? "HTTP/1.1" : "HTTP/1.0");
       }
   
       /**
  -     * Determines if the provided value is a valid IPv4
  -     * internet address.  
  +     * Determines if the provided value is a valid IPv4 internet address.
  +     * 
        * @param value - value to check
  +     * 
        * @return boolean - true if value is valid, otherwise false
  -     *       
        */
       private static boolean isIpAddress(String value) {
           log.trace("enter HttpMethodBase.isIpAddress(String)");
  @@ -1662,7 +1696,7 @@
               while (tokenizer.hasMoreTokens()) {
                   try {
                       int i = Integer.parseInt(tokenizer.nextToken());
  -                    if (i < 0 || i > 255) {
  +                    if ((i < 0) || (i > 255)) {
                           // parsed section of address is not in the proper range
                           return false;
                       }
  @@ -1677,52 +1711,288 @@
           return true;
       }
   
  -    // ----------------------------------------------------- Instance Variables
  -    /** My request path. */
  -    private String path = null;
  -    /** Whether or not I should automatically follow redirects. */
  -    private boolean followRedirects = false;
  -    /** My query string, if any. */
  -    private String queryString = null;
  -    /** My request headers, if any. */
  -    private HashMap requestHeaders = new HashMap();
  -    /** My response headers, if any. */
  -    private HashMap responseHeaders = new HashMap();
  -    /** My response status code, if any. */
  -    private int statusCode = -1;
  -    /** My response status text, if any. */
  -    private String statusText = null;
  -    /** Whether or not I have been executed. */
  -    private boolean used = false;
  -    /** Whether or not I should use the HTTP/1.1 protocol. */
  -    private boolean http11 = true;
  -    /** Whether or not the request body has been sent. */
  -    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;
  -    /** Maximum number of redirects and authentications that will be followed. */
  -    private static int maxForwards = 100;
  -    /** True if we're in strict mode. */
  -    private boolean strictMode = true;
  +    /**
  +     * "It must be possible to combine the multiple header fields into one
  +     * "field-name: field-value" pair, without changing the semantics of the
  +     * message, by appending each subsequent field-value to the first, each
  +     * separated by a comma." 
  +     * //TODO: This method is trying to make up for deficiencies in Header.
  +     * 
  +     * @param existingHeader the current header
  +     * @param value DOCUMENT ME!
  +     * 
  +     * @return DOCUMENT ME!
  +     */
  +    private String getNewHeaderValue(Header existingHeader, String value) {
  +        String existingValue = existingHeader.getValue();
  +        if (existingValue == null) {
  +            existingValue = "";
  +        }
  +        String newValue = value;
  +        if (value == null) {
  +            newValue = "";
  +        }
  +        return existingValue + ", " + newValue;
  +    }
   
  -    // -------------------------------------------------------------- Constants
  +    /**
  +     * Sets the specified response header.
  +     * 
  +     * @param header the header to set.
  +     * 
  +     * @since 2.0
  +     */
  +    private void setResponseHeader(Header header) {
  +        if (header == null) {
  +            return;
  +        }
  +        responseHeaders.put(header.getName().toLowerCase(), header);
  +    }
   
  -    /** Log object for this class. */
  -    private static final Log log = LogFactory.getLog(HttpMethod.class);
  +    /**
  +     * 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) {
  +        log.trace("enter HttpMethodBase.canResponseHaveBody(int)");
   
  -    /** Log for any wire messages. */
  -    private static final Log wireLog = LogFactory.getLog("httpclient.wire");
  +        boolean result = true;
   
  -    /** The User-Agent header sent on every request. */
  -    protected static final Header USER_AGENT;
  +        if ((status >= 100 && status <= 199) || (status == 204)
  +            || (status == 304)) { // NOT MODIFIED
  +            result = false;
  +        }
   
  -    static {
  -        String agent = System.getProperties().getProperty(
  -                "httpclient.useragent", "Jakarta Commons-HttpClient/2.0M1");
  -        USER_AGENT = new Header("User-Agent", agent);
  +        return result;
       }
   
  -}
  +    /**
  +     * Close the provided HTTP connection, if: http 1.0 and not using the
  +     * 'connect' method, or http 1.1 and the Connection: close header is sent
  +     * 
  +     * @param connection the HTTP connection to process
  +     */
  +    private void closeConnection(HttpConnection connection) {
  +        log.trace("enter closeConnection(HttpConnection)");
   
  +        if (!http11) {
  +            if (getName().equals(ConnectMethod.NAME)
  +                && (statusCode == HttpStatus.SC_OK)) {
  +                log.debug("Leaving connection open for tunneling");
  +            } else {
  +                log.debug("Closing connection since using HTTP/1.0, "
  +                          + "ConnectMethod and status is OK");
  +                connection.close();
  +            }
  +        } else {
  +            Header connectionHeader = getResponseHeader("connection");
  +            if ((null != connectionHeader)
  +                && "close".equalsIgnoreCase(connectionHeader.getValue())) {
  +                log.debug("Closing connection since \"Connection: close\" "
  +                    + "header found.");
  +                connection.close();
  +            }
  +        }
  +    }
  +
  +    /**
  +     * Generates a key used for idenifying visited URLs.
  +     * 
  +     * @param conn DOCUMENT ME!
  +     * 
  +     * @return DOCUMENT ME!
  +     */
  +    private String generateVisitedKey(HttpConnection conn) {
  +        return conn.getHost() + ":" + conn.getPort() + "|"
  +               + generateRequestLine(conn, getName(), getPath(), 
  +                                     getQueryString(), getHttpVersion());
  +    }
  +
  +    /**
  +     * process a response that requires authentication
  +     * 
  +     * @param state the current state
  +     * @param connection the connection for communication
  +     * 
  +     * @return true if the request has completed process, false if more
  +     *         attempts are needed
  +     */
  +    private boolean processAuthenticationResponse(HttpState state, 
  +                                                  HttpConnection connection) {
  +        log.trace("enter HttpMethodBase.processAuthenticationResponse("
  +            + "HttpState, HttpConnection)");
  +
  +        Set realms = new HashSet();
  +        Set proxyRealms = new HashSet();
  +
  +        // handle authentication required
  +        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;
  +        }
  +        boolean authenticated = false;
  +        // if there was a header requesting authentication
  +        if (null != wwwauth) {
  +            String pathAndCreds = getPath() + ":" + wwwauth.getValue();
  +            if (realmsUsed.contains(pathAndCreds)) {
  +                if (log.isInfoEnabled()) {
  +                    log.info("Already tried to authenticate to \""
  +                             + wwwauth.getValue() + "\" but still receiving "
  +                             + statusCode + ".");
  +                }
  +                return true;
  +            } else {
  +                realmsUsed.add(pathAndCreds);
  +            }
  +
  +            try {
  +                //remove preemptive header and reauthenticate
  +                switch (statusCode) {
  +                    case HttpStatus.SC_UNAUTHORIZED:
  +                        removeRequestHeader(Authenticator.WWW_AUTH_RESP);
  +                        authenticated = Authenticator.authenticate(this, state);
  +                        break;
  +                    case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
  +                        removeRequestHeader(Authenticator.PROXY_AUTH_RESP);
  +                        authenticated = Authenticator.authenticateProxy(this, 
  +                                                                        state);
  +                        break;
  +                }
  +            } catch (HttpException httpe) {
  +                log.warn(httpe.getMessage());
  +                return true; // finished request
  +            } catch (UnsupportedOperationException uoe) {
  +                log.warn(uoe.getMessage());
  +                //FIXME: should this return true?
  +            }
  +
  +            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
  +    }
  +
  +    /**
  +     * Write a request and read the response. Both the write to the server will
  +     * be retried {@link #maxRetries} times if the operation fails with a
  +     * HttpRecoverableException. The write will only be attempted if the read
  +     * has succeeded.
  +     * 
  +     * <p>
  +     * The <i>used</i> is set to true if the write succeeds.
  +     * </p>
  +     * 
  +     * @param state the current state
  +     * @param connection the connection for communication
  +     * 
  +     * @throws HttpException when errors occur as part of the HTTP protocol
  +     *         conversation
  +     * @throws IOException when an I/O error occurs communicating with the
  +     *         server
  +     * 
  +     * @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)");
  +
  +        //try to do the write
  +        int retryCount = 0;
  +        do {
  +            retryCount++;
  +            if (log.isTraceEnabled()) {
  +                log.trace("Attempt number " + retryCount + " to write request");
  +            }
  +            try {
  +                if (!connection.isOpen()) {
  +                    log.debug("Opening the connection.");
  +                    connection.open();
  +                }
  +                writeRequest(state, connection);
  +                used = true; //write worked, mark this method as used
  +                break; //move onto the write
  +            } catch (HttpRecoverableException httpre) {
  +                if (log.isDebugEnabled()) {
  +                    log.debug("Closing the connection.");
  +                }
  +
  +                connection.close();
  +                log.info("Recoverable exception caught when writing request");
  +                if (retryCount == maxRetries) {
  +                    log.warn(
  +                        "Attempt to write request has reached max retries: "
  +                        + maxRetries);
  +                    throw httpre;
  +                }
  +            }
  +        } while (retryCount <= maxRetries);
  +
  +        //try to do the read
  +        try {
  +            readResponse(state, connection);
  +        } catch (HttpRecoverableException httpre) {
  +            log.warn("Recoverable exception caught when reading response");
  +            if (log.isDebugEnabled()) {
  +                log.debug("Closing the connection.");
  +            }
  +
  +            connection.close();
  +            throw httpre;
  +        }
  +        //everything should be OK at this point
  +    }
  +
  +    /**
  +     * On a {@link HttpStatus#SC_CONTINUE continue}, if there are more request
  +     * bytes to be sent, write them to the connection
  +     * 
  +     * @param state the current state
  +     * @param connection the connection for communication
  +     * 
  +     * @throws HttpException when errors occur as part of the HTTP protocol
  +     *         conversation
  +     * @throws IOException when an I/O error occurs communicating with the
  +     *         server
  +     */
  +    private void writeRemainingRequestBody(HttpState state, 
  +                                           HttpConnection connection)
  +    throws HttpException, IOException {
  +        log.trace("enter writeRemainingRequestBody(HttpState, HttpConnection)");
  +
  +        if (HttpStatus.SC_CONTINUE == statusCode) {
  +            if (!bodySent) {
  +                bodySent = writeRequestBody(state, connection);
  +            } else {
  +                log.warn("Received status CONTINUE but he body has already been"
  +                    + "sent");
  +                // According to RFC 2616 this respose should be ignored
  +            }
  +            readResponse(state, connection);
  +        }
  +    }
  +}
  \ No newline at end of file
  
  
  

--
To unsubscribe, e-mail:   <mailto:commons-dev-unsubscribe@jakarta.apache.org>
For additional commands, e-mail: <mailto:commons-dev-help@jakarta.apache.org>


Mime
View raw message