hc-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Michael Becke <be...@u.washington.edu>
Subject Re: [PATCH] close on input streams handled properly - redux
Date Wed, 04 Dec 2002 23:21:11 GMT
Wow.  Nice work!

I'm having trouble applying this patch, so I can't check for myself,  
but I am wondering if this handles the case when the response stream is  
neither chunked nor has a content length.  It sounds like the  
ResponseAutoReleaseInputStream has been removed and previously this was  
the only stream which handled this case.

Thanks,

Mike

On Wednesday, December 4, 2002, at 05:44 PM, Eric Johnson wrote:

> All right, another try, dealing with the complexities of Michael's  
> changes.
>
> This patch is a top-to-bottom revisting of my previous patch.   
> Highlights:
>
>    * As before AutoCloseInputStream, ContentLengthInputStream, and
>      ChunkedInputStream no longer "close" their wrapped stream.  Now,
>      however, when the content is consumed, an optional
>      ResponseConsumedWatcher will be notified via the function
>      "responseConsumed".
>    * New interface ResponseConsumedWatcher - so named as I didn't want
>      to use the term "Listener", which usually implies in Java that
>      there can be multiple "listeners".  In this case, there can be
>      only one "watcher", and it may optionally be provided for the
>      constructor.  The interface is public only because the various
>      input stream wrappers are also public.
>    * The function HttpMethodBase.wrapResponseStream() is no longer
>      needed, and neither is its corresponding class
>      ResponseAutoReleaseInputStream.
>    * As a result of the preceding two changes, any time a response body
>      is consumed, it is funnelled through the responseBodyConsumed()
>      function, meaning there is just one(!) place to go to look for the
>      logic as to what happens at the end of a response.  You no longer
>      need to look at the interaction of AutoCloseInputStream,
>      ContentLengthInputStream, and ResponseAutoReleaseInputStream.
>    * MultiThreadedHttpConnectionManager.releaseConnection() - now
>      "finishes" the previous response before adding a connection to its
>      set.
>    * SimpleHttpConnectionManager.releaseConnection - finishes previous
>      response and insures that the connection being released matches
>      the connection it is tracking.  Also "finishes" a response prior
>      to returning it, as per email that Michael and I sent to this  
> group.
>    * No changes to HttpClient class!
>    * HttpMethodBase.releaseConnection() function merely calls "close"
>      on the responseStream - which triggers a responseConsumed() call.
>    * HttpMethodBase.execute() - now has a single exit point, and has
>      been subdivided into three functions.  The execute() function
>      itself is wrapped in a try/finally block to insure that state is
>      correct at the end of the execute call.
>
> It turns out that there were some additional corner cases that I  
> missed previously with regards to my previous patch.  I think I've  
> simplified the model now, and guaranteed that all the cases get  
> caught.  Central to this change is the single exit point for the  
> execute() function.
>
> The execute() function now sets internal state indicating that a  
> connection should be released when the execute() call is done.  At the  
> beginning of reading the response body, this flag gets reset.  At any  
> point after that (still in the execute() function), should the  
> responseConsumed() function be invoked, the flag will be set to  
> "release" the connection when execute is done.  If execute()  
> completes, and the connection needs to remain open, and  
> responseConsumed() is subsequently triggered, the connection will then  
> be released.  If the response is never closed, or read in its  
> entirety, one of two things will happen.  When using  
> SimpleHttpConnectionManager, a subsequent request will close the  
> previous response.  When using the MultiThreadedHttpConnectionManager,  
> the response will remain open until garbage collected (or consumed, or  
> closed, of course).
>
> An explicit call to releaseConnection() on the method simply closes  
> the input stream, which cascades to closing the connection (when  
> called outside of execute).
>
> One minor subtlety, the responseConsumed() function can be invoked  
> multiple times with a single response, particularly when an  
> AutoCloseInputStream wraps a ContentLengthInputStream or  
> ChunkedInputStream (that is to say, for example, a response that  
> includes both a "content-length" header and a "connection: close"  
> header).  The code accommodates this detail, but I wish it was > cleaner.
>
> I hope this patch passes muster this time around!  I appreciate  
> everyone's patience.
>
> -Eric Johnson
>
> ? close.patch
> ? close2.patch
> ? temp
> ? lib/junit.jar
> ? lib/servlet.jar
> ? src/java/org/apache/commons/httpclient/ResponseConsumedWatcher.java
> Index: src/java/org/apache/commons/httpclient/AutoCloseInputStream.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/AutoCloseInputStream.java,v
> retrieving revision 1.2
> diff -u -r1.2 AutoCloseInputStream.java
> --- src/java/org/apache/commons/httpclient/AutoCloseInputStream.java	9  
> Sep 2002 05:43:39 -0000	1.2
> +++ src/java/org/apache/commons/httpclient/AutoCloseInputStream.java	4  
> Dec 2002 21:59:38 -0000
> @@ -68,43 +68,58 @@
>
>  /**
>   * Closes a HttpConnection as soon as the end of the stream is  
> reached.
> + *
> + * <p>Note that this class never directly closes the "wrapped" stream  
> from which
> + * it reads, relying on the close of the connection to do that.</p>
> + *
>   * @author Ortwin Gl¸ck
>   *
>   * @since 2.0
>   */
>
>  class AutoCloseInputStream extends FilterInputStream {
> -    /** the connection the input stream comes from */
> -    private HttpConnection conn;
> +
> +    // assume that the underlying stream is open until we get an EOF  
> indication.
> +    private boolean streamOpen = true;
> +
> +    private boolean selfClosed = false;
> +
> +    /** The watcher is notified when the contents of the stream have  
> been exhausted */
> +    private ResponseConsumedWatcher watcher = null;
>
>      /**
>       * Create a new auto closing stream for the provided connection
> -     *
> +     *
>       * @param in the input stream to read from
> -     * @param conn the connection to close when done reading
> +     * @param watcher   To be notified when the contents of the  
> stream have been
> +     *  consumed.
>       */
> -    public AutoCloseInputStream(InputStream in, HttpConnection conn) {
> +    public AutoCloseInputStream(InputStream in,  
> ResponseConsumedWatcher watcher) {
>          super(in);
> -        this.conn = conn;
> +        this.watcher = watcher;
>      }
>
>      /**
>       * Reads the next byte of data from the input stream.
> -     *
> +     *
>       * @throws IOException when there is an error reading
>       * @return the character read, or -1 for EOF
>       */
>      public int read() throws IOException {
> -        int l = super.read();
> -        if (l == -1) {
> -            conn.close();
> +        int l = -1;
> +
> +        if (isReadAllowed()) {
> +            // underlying stream not closed, go ahead and read.
> +            l = super.read();
> +            checkClose(l);
>          }
> +
>          return l;
>      }
>
>      /**
>       * Reads up to <code>len</code> bytes of data from the stream.
> -     *
> +     *
>       * @param b a <code>byte</code> array to read data into
>       * @param off an offset within the array to store data
>       * @param len the maximum number of bytes to read
> @@ -112,26 +127,79 @@
>       * @throws IOException if there are errors reading
>       */
>      public int read(byte[] b, int off, int len) throws IOException {
> -        int l = super.read(b,  off,  len);
> -        if (l == -1) {
> -            conn.close();
> +        int l = -1;
> +
> +        if ( isReadAllowed() ) {
> +            l = super.read(b,  off,  len);
> +            checkClose(l);
>          }
> +
>          return l;
>      }
>
>      /**
>       * Reads some number of bytes from the input stream and stores  
> them into the
>       * buffer array b.
> -     *
> +     *
>       * @param b a <code>byte</code> array to read data into
>       * @return the number of bytes read or -1 for EOF
>       * @throws IOException if there are errors reading
>       */
>      public int read(byte[] b) throws IOException {
> -        int l = super.read(b);
> -        if (l == -1) {
> -            conn.close();
> +        int l = -1;
> +
> +        if ( isReadAllowed() ) {
> +            l = super.read(b);
> +            checkClose(l);
>          }
>          return l;
>      }
> -}
> \ No newline at end of file
> +
> +    /**
> +     * Close the stream, and also close the underlying stream if it  
> is not
> +     * already closed.
> +     */
> +    public void close() throws IOException {
> +        if (!selfClosed) {
> +            selfClosed = true;
> +            notifyWatcher();
> +        }
> +    }
> +
> +    /**
> +     * Close the underlying stream should the end of the stream  
> arrive.
> +     *
> +     * @param readResult    The result of the read operation to check.
> +     */
> +    private void checkClose(int readResult) {
> +        if (readResult == -1) {
> +            notifyWatcher();
> +        }
> +    }
> +
> +    /**
> +     * See whether a read of the underlying stream should be allowed,  
> and if
> +     * not, check to see whether our stream has already been closed!
> +     *
> +     * @return <code>true</code> if it is still OK to read from the  
> stream.
> +     */
> +    private boolean isReadAllowed() throws IOException {
> +        if (!streamOpen && selfClosed) {
> +            throw new IOException("Attempted read on closed stream.");
> +        }
> +        return streamOpen;
> +    }
> +
> +    /**
> +     * Notify the watcher that the contents have been consumed.
> +     */
> +    private void notifyWatcher() {
> +        if (streamOpen) {
> +            streamOpen = false;
> +
> +            if (watcher != null)
> +                watcher.responseConsumed();
> +        }
> +    }
> +}
> +
> Index: src/java/org/apache/commons/httpclient/ChunkedInputStream.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/ChunkedInputStream.java,v
> retrieving revision 1.6
> diff -u -r1.6 ChunkedInputStream.java
> --- src/java/org/apache/commons/httpclient/ChunkedInputStream.java	16  
> Oct 2002 13:14:11 -0000	1.6
> +++ src/java/org/apache/commons/httpclient/ChunkedInputStream.java	4  
> Dec 2002 21:59:38 -0000
> @@ -63,12 +63,16 @@
>  package org.apache.commons.httpclient;
>
>  import java.io.*;
> -import java.util.*;
>
>  /**
>   * <p>Transparently coalesces chunks of a HTTP stream that uses  
> Transfer-Encoding
>   * chunked.</p>
>   *
> + * <p>Note that this class NEVER closes the underlying stream, even  
> when close gets
> + * called.  Instead, it will read until the "end" of its chunking on  
> close, which
> + * allows for the seamless invocation of subsequent HTTP 1.1 calls,  
> while not
> + * requiring the client to remember to read the entire contents of  
> the response.</p>
> + *
>   * @see ResponseInputStream
>   *
>   * @author Ortwin Gl¸ck
> @@ -83,9 +87,12 @@
>      private InputStream in;
>      private int chunkSize, pos;
>      private boolean eof = false;
> +    private boolean closed = false;
>      private static final String HTTP_ENC = "US-ASCII";
>      private HttpMethod method;
>
> +    private ResponseConsumedWatcher watcher = null;
> +
>      /**
>       *
>       *
> @@ -96,7 +103,7 @@
>       * @throws java.lang.NullPointerException
>       *
>       */
> -    public ChunkedInputStream(final InputStream in, final HttpMethod  
> method) throws IOException {
> +    public ChunkedInputStream(InputStream in, HttpMethod method,  
> ResponseConsumedWatcher watcher) throws IOException {
>        if (null == in) {
>          throw new NullPointerException("InputStream parameter");
>        }
> @@ -105,6 +112,7 @@
>        }
>          this.in = in;
>          this.method = method;
> +        this.watcher = watcher;
>          this.chunkSize = getChunkSizeFromInputStream(in);
>          if (chunkSize == 0) {
>              eof = true;
> @@ -124,6 +132,9 @@
>       * @throws IOException
>       */
>      public int read() throws IOException {
> +
> +        if (closed)
> +            throw new IOException("Attempted read from closed  
> stream.");
>          if (eof) return -1;
>          if (pos >= chunkSize) {
>              nextChunk();
> @@ -134,6 +145,10 @@
>      }
>
>      public int read(byte[] b, int off, int len) throws  
> java.io.IOException {
> +
> +        if (closed)
> +            throw new IOException("Attempted read from closed  
> stream.");
> +
>          if (eof) return -1;
>          if (pos >= chunkSize) {
>              nextChunk();
> @@ -252,6 +267,11 @@
>              }
>              line = readLine();
>          }
> +
> +        // notify the watcher that the response has been consumed.
> +        if (watcher != null) {
> +            watcher.responseConsumed();
> +        }
>      }
>
>      private String readLine() throws IOException {
> @@ -274,7 +294,45 @@
>          return (buf.toString());
>      }
>
> +    /**
> +     * Upon close, this reads the remainder of the chunked message,
> +     * leaving the underlying socket at a position to start reading  
> the
> +     * next response without scanning.
> +     */
>      public void close() throws IOException {
> -        in.close();
> +        if (!closed) {
> +            try {
> +                if (!eof) {
> +                    exhaustInputStream(this);
> +                }
> +            }
> +            finally {
> +                // on the off chance that an error occurred while  
> consuming
> +                // the remainder of the response, notify the watcher.
> +                if (!eof && watcher != null) {
> +                    watcher.responseConsumed();
> +                }
> +                eof = true;
> +                closed = true;
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Exhaust an input stream, reading until EOF has been  
> encountered.
> +     *
> +     * <p>Note that this function is intended as a non-public utility.
> +     * This is a little weird, but it seemed silly to make a utility
> +     * class for this one function, so instead it is just static and
> +     * shared that way.</p>
> +     *
> +     * @param inStream The {@link InputStream} to exhaust.
> +     */
> +    static void exhaustInputStream(InputStream inStream) throws  
> IOException {
> +        // read and discard the remainder of the message
> +        byte buffer[] = new byte[1024];
> +        while ( inStream.read(buffer) >= 0) {
> +            ;
> +        }
>      }
>  }
> Index:  
> src/java/org/apache/commons/httpclient/ContentLengthInputStream.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/ContentLengthInputStream.java,v
> retrieving revision 1.2
> diff -u -r1.2 ContentLengthInputStream.java
> ---  
> src/java/org/apache/commons/httpclient/ContentLengthInputStream.java	 
> 19 Sep 2002 10:15:08 -0000	1.2
> +++  
> src/java/org/apache/commons/httpclient/ContentLengthInputStream.java	4  
> Dec 2002 21:59:38 -0000
> @@ -75,6 +75,12 @@
>      private int contentLength;
>      private int pos = 0;
>
> +    private boolean closed = false;
> +    private boolean watcherNotified = false;
> +
> +    /** The watcher is notified when the contents of the stream have  
> been exhausted */
> +    private ResponseConsumedWatcher watcher;
> +
>      /**
>       * Creates a new length limited stream
>       *
> @@ -82,20 +88,45 @@
>       * @param contentLength The maximum number of bytes that can be  
> read from
>       * the stream. Subsequent read operations will return -1.
>       */
> -    public ContentLengthInputStream(InputStream in, int  
> contentLength) {
> +    public ContentLengthInputStream(InputStream in, int  
> contentLength, ResponseConsumedWatcher watcher) {
>          super(in);
> +        this.watcher = watcher;
>          this.contentLength = contentLength;
>      }
>
>      public int read() throws java.io.IOException {
> -        if (pos >= contentLength) return -1;
> +        if (closed)
> +            throw new IOException("Attempted read from closed  
> stream.");
> +
> +        if (pos >= contentLength) {
> +            notifyWatcher();
> +            return -1;
> +        }
>          pos++;
>          return super.read();
>      }
>
> -
> +    /**
> +     * Does standard {@link InputStream#read(byte[], int, int)}  
> behavior, but
> +     * also notifies the watcher when the contents have been consumed.
> +     *
> +     * @param b     The byte array to fill.
> +     * @param off   Start filling at this position.
> +     * @param len   The number of bytes to attempt to read.
> +     * @return The number of bytes read, or -1 if the end of content  
> has been
> +     *  reached.
> +     *
> +     * @throws java.io.IOException Should an error occur on the  
> wrapped stream.
> +     */
>      public int read(byte[] b, int off, int len) throws  
> java.io.IOException {
> -        if (pos >= contentLength) return -1;
> +        if (closed)
> +            throw new IOException("Attempted read from closed  
> stream.");
> +
> +        if (pos >= contentLength) {
> +            notifyWatcher();
> +            return -1;
> +        }
> +
>          if (pos + len > contentLength) {
>              len = contentLength - pos;
>          }
> @@ -104,9 +135,41 @@
>          return count;
>      }
>
> -
>      public int read(byte[] b) throws java.io.IOException {
>          return read(b, 0, b.length);
>      }
>
> +    /**
> +     * Reads until the end of the known length of content.
> +     *
> +     * <p>Does not close the underlying socket input, but instead  
> leaves it
> +     * primed to parse the next response.</p>
> +     */
> +    public void close() throws IOException {
> +        if (!closed) {
> +            try {
> +                ChunkedInputStream.exhaustInputStream(this);
> +            } finally {
> +                // close after above so that we don't throw an  
> exception trying
> +                // to read after closed!
> +                closed = true;
> +                // note that this notify should be mostly redundant,  
> except
> +                // in cases where some unexpected IO problem occurs  
> on the
> +                // underlying stream.
> +                notifyWatcher();
> +            }
> +        }
> +    }
> +
> +    /**
> +     * Notify the watcher that the contents have been consumed.
> +     */
> +    private void notifyWatcher() {
> +        if (!watcherNotified) {
> +            watcherNotified = true;
> +
> +            if (watcher != null)
> +                watcher.responseConsumed();
> +        }
> +    }
>  }
> Index: src/java/org/apache/commons/httpclient/HttpConnection.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/HttpConnection.java,v
> retrieving revision 1.26
> diff -u -r1.26 HttpConnection.java
> --- src/java/org/apache/commons/httpclient/HttpConnection.java	3 Dec  
> 2002 05:46:15 -0000	1.26
> +++ src/java/org/apache/commons/httpclient/HttpConnection.java	4 Dec  
> 2002 21:59:39 -0000
> @@ -306,6 +306,37 @@
>          return (!(null == _proxyHost || 0 >= _proxyPort));
>      }
>
> +    /**
> +     * Set the state to keep track of the response for a single  
> request.
> +     *
> +     * <p>HttpClient uses this to insure that previous requests are  
> properly
> +     * closed before a new request is attempted.  That way, a GET  
> request need
> +     * not be read in its entirety before a new request is issued.   
> Instead,
> +     * the stream can be closed as appropriate.</p>
> +     *
> +     * @param inStream  The stream associated with an HttpMethod.
> +     */
> +    public void setSingleResponseInputStream(InputStream inStream) {
> +        _singleResponseInput = inStream;
> +    }
> +
> +    /**
> +     * Returns the stream used to read the last response's body.
> +     *
> +     * <p>Clients will generally not need to call this function unless
> +     * using HttpConnection directly, instead of calling {@link  
> HttpClient#executeMethod}.
> +     * For those clients, call this function, and if it returns a  
> non-null stream,
> +     * close the stream before attempting to execute a method.  Note  
> that
> +     * calling "close" on the stream returned by this function  
> <i>may</i> close
> +     * the connection if the previous response contained a  
> "Connection: close" header. </p>
> +     *
> +     * @return An {@link InputStream} corresponding to the body of  
> the last
> +     *  response.
> +     */
> +    public InputStream getSingleResponseInputStream() {
> +        return _singleResponseInput;
> +    }
> +
>      // --------------------------------------------------- Other  
> Public Methods
>
>      /**
> @@ -776,6 +807,9 @@
>      protected void closeSocketAndStreams() {
>          log.trace("enter HttpConnection.closeSockedAndStreams()");
>
> +        // no longer care about previous responses...
> +        _singleResponseInput = null;
> +
>          if (null != _input) {
>              try {
>                  _input.close();
> @@ -891,6 +925,8 @@
>      private InputStream _input = null;
>      /** My OutputStream. */
>      private OutputStream _output = null;
> +    /** An {@link InputStream} for the response to an individual  
> request. */
> +    private InputStream _singleResponseInput = null;
>      /** Whether or not I am connected. */
>      private boolean _open = false;
>      /** Whether or not I am/should connect via SSL. */
> Index: src/java/org/apache/commons/httpclient/HttpMethodBase.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/HttpMethodBase.java,v
> retrieving revision 1.83
> diff -u -r1.83 HttpMethodBase.java
> --- src/java/org/apache/commons/httpclient/HttpMethodBase.java	3 Dec  
> 2002 15:28:18 -0000	1.83
> +++ src/java/org/apache/commons/httpclient/HttpMethodBase.java	4 Dec  
> 2002 21:59:41 -0000
> @@ -233,12 +233,19 @@
>      /** Whether or not I have been executed. */
>      private boolean used = false;
>
> +    /** How many times did this transparently handle a recoverable  
> exception? */
> +    private int recoverableExceptionCount = 0;
> +
>      /**
>       * The maximum number of attempts to attempt recovery from an
>       * HttpRecoverableException.
>       */
>      private int maxRetries = 3;
>
> +    private boolean inExecute = false;
> +
> +    private boolean doneWithConnection = false;
> +
>      /** Default content encoding chatset */
>      protected static final String DEFAULT_CHARSET = "ISO-8859-1";
>
> @@ -566,6 +573,8 @@
>                  while ((len = is.read(buffer)) > 0) {
>                      os.write(buffer, 0, len);
>                  }
> +                is.close();
> +                os.close();
>                  responseBody = os.toByteArray();
>                  setResponseStream(null);
>                  log.debug("buffering response body");
> @@ -590,9 +599,9 @@
>              return responseStream;
>          }
>          if (responseBody != null) {
> -            responseStream = new ByteArrayInputStream(responseBody);
> +            InputStream byteResponseStream = new  
> ByteArrayInputStream(responseBody);
>              log.debug("re-creating response stream from byte array");
> -            return responseStream;
> +            return byteResponseStream;
>          }
>          return null;
>      }
> @@ -717,25 +726,7 @@
>          setRequestHeader(header);
>      }
>
> -
> -    /**
> -     * 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. 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.
> -     */
> -    private void closeConnection(HttpConnection connection) {
> -        if (shouldCloseConnection()) {
> -            connection.close();
> -        }
> -    }
> -
> -    private boolean shouldCloseConnection() {
> +    protected boolean shouldCloseConnection() {
>          if (!http11) {
>              if (getName().equals(ConnectMethod.NAME) &&
>                      (statusLine.getStatusCode() == HttpStatus.SC_OK))  
> {
> @@ -757,13 +748,59 @@
>          return false;
>      }
>
> -    private void wrapResponseStream( HttpConnection connection ) {
> +    private boolean isRetryNeeded(int statusCode, HttpState state,  
> HttpConnection conn) {
> +        switch (statusCode) {
> +            case HttpStatus.SC_UNAUTHORIZED:
> +            case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
> +                log.debug("Authorization required");
> +                if (doAuthentication) { //process authentication  
> response
> +                    //if the authentication is successful, return the  
> statusCode
> +                    //otherwise, drop through the switch and try  
> again.
> +                    if (processAuthenticationResponse(state)) {
> +                        return false;
> +                    }
> +                } else { //let the client handle the authenticaiton
> +                    return false;
> +                }
> +                break;
> +
> +            case HttpStatus.SC_MOVED_TEMPORARILY:
> +            case HttpStatus.SC_MOVED_PERMANENTLY:
> +            case HttpStatus.SC_TEMPORARY_REDIRECT:
> +                log.debug("Redirect required");
> +
> +                if (! processRedirectResponse(conn)) {
> +                    return false;
> +                }
> +                break;
> +
> +            default:
> +                // neither an unauthorized nor a redirect response
> +                return false;
> +        } //end of switch
> +
> +        return true;
> +    }
>
> -        if ( responseStream != null ) {
> -            this.responseConnection = connection;
> -            this.responseStream = new  
> ResponseAutoReleaseInputStream(responseStream);
> +    private void checkExecuteConditions(HttpState state,  
> HttpConnection conn)
> +    throws HttpException {
> +
> +        if (null == state) {
> +            throw new NullPointerException("HttpState parameter");
> +        }
> +        if (null == conn) {
> +            throw new NullPointerException("HttpConnection  
> parameter");
> +        }
> +        if (hasBeenUsed()) {
> +            throw new HttpException("Already used, but not  
> recycled.");
> +        }
> +        if (!validate()) {
> +            throw new HttpException("Not valid");
>          }
>
> +        if (inExecute) {
> +            throw new IllegalStateException("Execute invoked  
> recursively, or exited abnormally.");
> +        }
>      }
>
>      /**
> @@ -793,104 +830,83 @@
>      throws HttpException, IOException, NullPointerException {
>          log.trace("enter HttpMethodBase.execute(HttpState,  
> HttpConnection)");
>
> -        //TODO: This method is too large
> -        //check some error conditions
> -        if (null == state) {
> -            throw new NullPointerException("HttpState parameter");
> -        }
> -        if (null == conn) {
> -            throw new NullPointerException("HttpConnection  
> parameter");
> -        }
> -        if (hasBeenUsed()) {
> -            throw new HttpException("Already used, but not  
> recycled.");
> -        }
> -        if (!validate()) {
> -            throw new HttpException("Not valid");
> -        }
> -
> -        //pre-emptively add the authorization header, if required.
> -        Authenticator.authenticate(this, state);
> -        if (conn.isProxied()) {
> -            Authenticator.authenticateProxy(this, state);
> -        }
> +        checkExecuteConditions(state, conn);
> +        inExecute = true;
>
> -        //Set visited = new HashSet();
> -        realms = new HashSet();
> -        proxyRealms = new HashSet();
> -        int forwardCount = 0; //protect from an infinite loop
> +        try {
> +            //TODO: This method is too large
>
> -        while (forwardCount++ < maxForwards) {
> -            if (log.isDebugEnabled()) {
> -                log.debug("Execute loop try " + forwardCount);
> -            }
> +            //pre-emptively add the authorization header, if required.
> +            Authenticator.authenticate(this, state);
> +            if (conn.isProxied()) {
> +                Authenticator.authenticateProxy(this, state);
> +            }
> +
> +            //Set visited = new HashSet();
> +            realms = new HashSet();
> +            proxyRealms = new HashSet();
> +            int forwardCount = 0; //protect from an infinite loop
> +
> +            while (forwardCount++ < maxForwards) {
> +                // on every retry, reset this state information.
> +                responseConnection = conn;
> +                conn.setSingleResponseInputStream(null);
>
> -            //write the request and read the response, will retry
> -            processRequest(state, conn);
> +                if (log.isDebugEnabled()) {
> +                    log.debug("Execute loop try " + forwardCount);
> +                }
>
> -            //if SC_CONTINUE write the request body
> -            writeRemainingRequestBody(state, conn);
> +                //write the request and read the response, will retry
> +                processRequest(state, conn);
>
> -            int statusCode = statusLine.getStatusCode();
> -            switch (statusCode) {
> -                case HttpStatus.SC_UNAUTHORIZED:
> -                case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
> -                    log.debug("Authorization required");
> -                    if (doAuthentication) { //process authentication  
> response
> -                        //if the authentication is successful, return  
> the statusCode
> -                        //otherwise, drop through the switch and try  
> again.
> -                        if (processAuthenticationResponse(state,  
> conn)) {
> -                            wrapResponseStream(conn);
> -                            return statusCode;
> -                        }
> -                    } else { //let the client handle the  
> authenticaiton
> -                        wrapResponseStream(conn);
> -                        return statusCode;
> -                    }
> -                    break;
> +                //if SC_CONTINUE write the request body
> +                writeRemainingRequestBody(state, conn);
>
> -                case HttpStatus.SC_MOVED_TEMPORARILY:
> -                case HttpStatus.SC_MOVED_PERMANENTLY:
> -                case HttpStatus.SC_TEMPORARY_REDIRECT:
> -                    log.debug("Redirect required");
> -
> -                    if (! processRedirectResponse(state, conn)) {
> -                        wrapResponseStream(conn);
> -                        return statusCode;
> -                    }
> +                if (!isRetryNeeded(statusLine.getStatusCode(), state,  
> conn)) {
> +                    // nope, no retry needed, exit loop.
>                      break;
> +                }
> +                /*
> +                   Revisiting may be desired. We do not know about  
> the server's internal state.
>
> -                default:
> -                    // neither an unauthorized nor a redirect response
> -                    wrapResponseStream(conn);
> +                //check to see if we have visited this url before
> +                if (visited.contains(generateVisitedKey(conn))) {
> +                    log.error("Link " + generateVisitedKey(conn) + "'  
> revisited");
>                      return statusCode;
> -            } //end of switch
> +                }
> +                visited.add(generateVisitedKey(conn));
> +                */
>
> -/*
> -    Revisiting may be desired. We do not know about the server's  
> internal state.
> +                // retry - close previous stream.  Caution - this  
> causes
> +                // responseBodyConsumed to be called, which may also  
> close the
> +                // connection.
> +                if (responseStream != null) {
> +                    responseStream.close();
> +                }
>
> -            //check to see if we have visited this url before
> -            if (visited.contains(generateVisitedKey(conn))) {
> -                log.error("Link " + generateVisitedKey(conn) + "'  
> revisited");
> -                return statusCode;
> -            }
> -            visited.add(generateVisitedKey(conn));
> -*/
> +            } //end of retry loop
>
> -            //close connection if required
> -            closeConnection(conn);
> -            if (conn.isOpen()) {
> -                //throw away body to position the stream after the  
> response
> -                getResponseBodyAsString();
> +            if (forwardCount >= maxForwards) {
> +                log.error("Narrowly avoided an infinite loop in  
> execute");
> +                throw new HttpRecoverableException("Maximum redirects  
> ("+ maxForwards +") exceeded");
> +            }
> +        }
> +        finally {
> +            inExecute = false;
> +            // If the response has been fully processed, return the  
> connection
> +            // to the pool.  Use this flag, rather than other tests  
> (like
> +            // responseStream == null), as subclasses, might reset  
> the stream,
> +            // for example, reading the entire response into a file  
> and then
> +            // setting the file as the stream.
> +            if (doneWithConnection) {
> +                insureConnectionRelease();
>              }
> -        } //end of loop
> -
> -        wrapResponseStream(conn);
> +        }
>
> -        log.error("Narrowly avoided an infinite loop in execute");
> -        throw new HttpRecoverableException("Maximum redirects ("+  
> maxForwards +") exceeded");
> +        return statusLine.getStatusCode();
>      }
>
> -    private boolean processRedirectResponse(HttpState state,  
> HttpConnection conn) {
> +    private boolean processRedirectResponse(HttpConnection conn) {
>
>          if (!getFollowRedirects()) {
>              log.info("Redirect requested but followRedirects is "
> @@ -965,8 +981,8 @@
>       * Check for a valid redirect given the current conn and new url.
>       * Redirect to a different protocol, host or port are checked for  
> validity.
>       *
> -     * @param conn The existing HttpConnection
> -     * @param url The new URL to redirect to
> +     * @param currentUrl The current URL (redirecting from)
> +     * @param redirectUrl The new URL to redirect to
>       * @throws HttpException if the redirect is invalid
>       * @since 2.0
>       */
> @@ -1054,6 +1070,9 @@
>          http11 = true;
>          bodySent = false;
>          responseBody = null;
> +        recoverableExceptionCount = 0;
> +        inExecute = false;
> +        doneWithConnection = false;
>      }
>
>      /**
> @@ -1063,10 +1082,13 @@
>       */
>      public void releaseConnection() {
>
> -        if ( responseConnection != null ) {
> -            responseConnection.releaseConnection();
> -            this.responseConnection = null;
> -            this.responseStream = null;
> +        if (responseStream != null) {
> +            try {
> +                // FYI - this may indirectly invoke  
> responseBodyConsumed.
> +                responseStream.close();
> +            } catch (IOException e) {
> +                // attempting cleanup, don't care about exception.
> +            }
>          }
>
>      }
> @@ -1568,9 +1590,10 @@
>       * 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).
> +     * The current implementation wraps the socket level stream with
> +     * an appropriate stream for the type of response (chunked,  
> content-length,
> +     * or auto-close).  If there is no response body, the connection  
> associated
> +     * with the request will be returned to the connection manager.
>       * </p>
>       *
>       * <p>
> @@ -1591,7 +1614,15 @@
>          log.trace(
>              "enter HttpMethodBase.readResponseBody(HttpState,  
> HttpConnection)");
>
> -        setResponseStream(_readResponseBody(state, conn));
> +        // assume we are not done with the connection if we get a  
> stream
> +        doneWithConnection = false;
> +        InputStream stream = _readResponseBody(conn);
> +        if (stream == null) {
> +            // done with the connection!
> +            finishWithConnection();
> +        }
> +        conn.setSingleResponseInputStream(stream);
> +        setResponseStream(stream);
>      }
>
>      /**
> @@ -1606,11 +1637,10 @@
>       * @see #readResponse
>       * @see #processResponseBody
>       *
> -     * @param state the client state
>       * @param conn the {@link HttpConnection} to read the response  
> from
>       * @return InputStream to read the response body from
>       */
> -    private InputStream _readResponseBody(HttpState state,  
> HttpConnection conn)
> +    private InputStream _readResponseBody(HttpConnection conn)
>      throws IOException {
>          log.trace("enter HttpMethodBase.readResponseBody(HttpState,  
> HttpConnection)");
>
> @@ -1626,7 +1656,7 @@
>          // RFC2616, 4.4 item number 3
>          if (null != transferEncodingHeader) {
>              if  
> ("chunked".equalsIgnoreCase(transferEncodingHeader.getValue())) {
> -                result = new ChunkedInputStream(is, this);
> +                result = new ChunkedInputStream(is, this,  
> m_responseWatcher);
>              }
>          } else if (null != lengthHeader) {
>
> @@ -1649,7 +1679,7 @@
>                  int expectedLength = Integer.parseInt(lengthValue);
>                  // FIXME: what if the content length is 0, perhaps we  
> should
>                  // just return an empty stream in that case
> -                result = new ContentLengthInputStream(is,  
> expectedLength);
> +                result = new ContentLengthInputStream(is,  
> expectedLength, m_responseWatcher);
>              } catch(NumberFormatException e) {
>                  throw new HttpException(
>                      "Unable to parse server response content length:  
> '"
> @@ -1667,9 +1697,10 @@
>          }
>
>          if (shouldCloseConnection()) {
> -            result = new AutoCloseInputStream(result, conn);
> +            result = new AutoCloseInputStream(result,  
> m_responseWatcher);
>          }
>
> +        // set the stream for this response....
>          return result;
>      }
>
> @@ -2065,29 +2096,14 @@
>      }
>
>      /**
> -     * 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) {
> +    private boolean processAuthenticationResponse(HttpState state) {
>          log.trace("enter  
> HttpMethodBase.processAuthenticationResponse("
>              + "HttpState, HttpConnection)");
>
> @@ -2175,8 +2191,8 @@
>       * @throws IOException when an I/O error occurs communicating  
> with the
>       *         server
>       *
> -     * @see writeRequest(HttpState,HttpConnection)
> -     * @see readResponse(HttpState,HttpConnection)
> +     * @see #writeRequest(HttpState,HttpConnection)
> +     * @see #readResponse(HttpState,HttpConnection)
>       */
>      private void processRequest(HttpState state, HttpConnection  
> connection)
>      throws HttpException, IOException {
> @@ -2203,6 +2219,9 @@
>                      log.debug("Closing the connection.");
>                  }
>
> +                // update the recoverable exception count.
> +                recoverableExceptionCount++;
> +
>                  connection.close();
>                  log.info("Recoverable exception caught when writing  
> request");
>                  if (retryCount == maxRetries) {
> @@ -2300,64 +2319,64 @@
>      }
>
>      /**
> -     * Releases this connection from its connectionManager when the  
> response has
> -     * been read.
> +     * Returns the number of "recoverable" exceptions thrown and  
> handled, to
> +     * allow for monitoring the quality of the connection.
> +     *
> +     * @return The number of recoverable exceptions handled by the  
> method.
>       */
> -    private class ResponseAutoReleaseInputStream extends InputStream {
> -
> -        private InputStream is;
> +    public int getRecoverableExceptionCount() {
> +        return recoverableExceptionCount;
> +    }
>
> -        public ResponseAutoReleaseInputStream(InputStream is) {
> -            this.is = is;
> -        }
> +    /**
> +     * A response has been consumed.
> +     *
> +     * The watcher can release the connection as appropriate.  Note  
> that watchers
> +     * should be prepared to receive multiple notifications that a  
> response has
> +     * been consumed.  This case might arise in the case of a  
> "Connection: close"
> +     * header on a response.
> +     */
> +    private void responseBodyConsumed() {
>
> -        /**
> -         * @see java.io.InputStream#close()
> -         */
> -        public void close() throws IOException {
> -            is.close();
> -            releaseConnection();
> +        // make sure this is the initial invocation of the  
> notification,
> +        // ignore subsequent ones.
> +        if (responseStream != null) {
> +            finishWithConnection();
>          }
> +    }
>
> -        /**
> -         * @see java.io.InputStream#read()
> -         */
> -        public int read() throws IOException {
> -            int b = is.read();
> -
> -            if ( b == -1 ) {
> -                releaseConnection();
> -            }
> +    private void finishWithConnection() {
> +        responseStream = null;
> +        responseConnection.setSingleResponseInputStream(null);
>
> -            return b;
> +        if (shouldCloseConnection()) {
> +            responseConnection.close();
>          }
>
> -        /**
> -         * @see java.io.InputStream#read(byte, int, int)
> -         */
> -        public int read(byte[] array, int off, int len) throws  
> IOException {
> -            int b = is.read(array, off, len);
> -
> -            if ( b == -1 ) {
> -                releaseConnection();
> -            }
> -
> -            return b;
> +        doneWithConnection = true;
> +        if (!inExecute) {
> +            insureConnectionRelease();
>          }
> +    }
>
> -        /**
> -         * @see java.io.InputStream#read(byte)
> -         */
> -        public int read(byte[] array) throws IOException {
> -            int b = is.read(array);
> -
> -            if ( b == -1 ) {
> -                releaseConnection();
> -            }
> -
> -            return b;
> +    /**
> +     * Insure that the connection is released back to the pool.
> +     */
> +    private void insureConnectionRelease() {
> +        if ( responseConnection != null ) {
> +            responseConnection.releaseConnection();
> +            responseConnection = null;
>          }
> -
>      }
> +
> +    /**
> +     * This exists so that the public interface to this class need  
> not include
> +     * either the responseConsumed or the responseBodyConsumed  
> methods.
> +     */
> +    private ResponseConsumedWatcher m_responseWatcher = new  
> ResponseConsumedWatcher() {
> +        public void responseConsumed() {
> +            responseBodyConsumed();
> +        }
> +    };
>
>  }
> Index:  
> src/java/org/apache/commons/httpclient/ 
> MultiThreadedHttpConnectionManager.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/MultiThreadedHttpConnectionManager.java,v
> retrieving revision 1.1
> diff -u -r1.1 MultiThreadedHttpConnectionManager.java
> ---  
> src/java/org/apache/commons/httpclient/ 
> MultiThreadedHttpConnectionManager.java	3 Dec 2002 05:46:15 -0000	1.1
> +++  
> src/java/org/apache/commons/httpclient/ 
> MultiThreadedHttpConnectionManager.java	4 Dec 2002 21:59:41 -0000
> @@ -349,6 +349,8 @@
>      public void releaseConnection(HttpConnection conn) {
>          log.trace("enter  
> HttpConnectionManager.releaseConnection(HttpConnection)");
>
> +        // make sure that the response has been read.
> +        SimpleHttpConnectionManager.finishLastResponse(conn);
>          String host = conn.getHost();
>          int port = conn.getPort();
>          String key = host + ":" + port;
> Index:  
> src/java/org/apache/commons/httpclient/> SimpleHttpConnectionManager.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/SimpleHttpConnectionManager.java,v
> retrieving revision 1.1
> diff -u -r1.1 SimpleHttpConnectionManager.java
> ---  
> src/java/org/apache/commons/httpclient/ 
> SimpleHttpConnectionManager.java	3 Dec 2002 05:46:15 -0000	1.1
> +++  
> src/java/org/apache/commons/httpclient/ 
> SimpleHttpConnectionManager.java	4 Dec 2002 21:59:42 -0000
> @@ -62,6 +62,8 @@
>  package org.apache.commons.httpclient;
>
>  import java.net.MalformedURLException;
> +import java.io.InputStream;
> +import java.io.IOException;
>
>
>  /**
> @@ -137,8 +139,10 @@
>                   
> httpConnection.setProxyHost(hostConfiguration.getProxyHost());
>                   
> httpConnection.setProxyPort(hostConfiguration.getProxyPort());
>
> -            }
> -
> +            }
> +            else {
> +                finishLastResponse(httpConnection);
> +            }
>          }
>
>          return httpConnection;
> @@ -149,6 +153,28 @@
>       * @see  
> org.apache.commons.httpclient.HttpConnectionManager#releaseConnection(o 
> rg.apache.commons.httpclient.HttpConnection)
>       */
>      public void releaseConnection(HttpConnection conn) {
> +        if (conn != httpConnection)
> +            throw new IllegalStateException("Unexpected close on a  
> different connection.");
> +
> +        finishLastResponse(httpConnection);
>      }
>
> +    /**
> +     * Since the same connection is about to be reused, make sure the
> +     * previous request was completely processed, and if not
> +     * consume it now.
> +     */
> +    static void finishLastResponse(HttpConnection conn) {
> +        InputStream lastResponse =  
> conn.getSingleResponseInputStream();
> +        if ( lastResponse != null) {
> +            conn.setSingleResponseInputStream(null);
> +            try {
> +                lastResponse.close();
> +            }
> +            catch (IOException ioe) {
> +                // badness - close to force reconnect.
> +                conn.close();
> +            }
> +        }
> +    }
>  }
> Index: src/java/org/apache/commons/httpclient/methods/GetMethod.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/methods/GetMethod.java,v
> retrieving revision 1.18
> diff -u -r1.18 GetMethod.java
> --- src/java/org/apache/commons/httpclient/methods/GetMethod.java	3  
> Sep 2002 01:36:26 -0000	1.18
> +++ src/java/org/apache/commons/httpclient/methods/GetMethod.java	4  
> Dec 2002 21:59:42 -0000
> @@ -372,6 +372,7 @@
>              while ((len = in.read(buffer)) > 0) {
>                  out.write(buffer, 0, len);
>              }
> +            in.close();
>              out.close();
>              setResponseStream(new FileInputStream(createTempFile()));
>          }
> Index: src/java/org/apache/commons/httpclient/methods/HeadMethod.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/methods/HeadMethod.java,v
> retrieving revision 1.11
> diff -u -r1.11 HeadMethod.java
> --- src/java/org/apache/commons/httpclient/methods/HeadMethod.java	1  
> Sep 2002 01:27:37 -0000	1.11
> +++ src/java/org/apache/commons/httpclient/methods/HeadMethod.java	4  
> Dec 2002 21:59:42 -0000
> @@ -146,8 +146,12 @@
>          log.trace(
>              "enter HeadMethod.readResponseBody(HttpState,  
> HttpConnection)");
>
> +        if (shouldCloseConnection()) {
> +            conn.close();
> +        }
> +
>          // despite the possible presence of a content-length header,
>          // HEAD returns no response body
>          return;
>      }
> -}
> \ No newline at end of file
> +}
> Index: src/java/org/apache/commons/httpclient/methods/PostMethod.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/java/org/apache/ 
> commons/httpclient/methods/PostMethod.java,v
> retrieving revision 1.27
> diff -u -r1.27 PostMethod.java
> --- src/java/org/apache/commons/httpclient/methods/PostMethod.java	12  
> Nov 2002 09:58:23 -0000	1.27
> +++ src/java/org/apache/commons/httpclient/methods/PostMethod.java	4  
> Dec 2002 21:59:43 -0000
> @@ -736,7 +736,9 @@
>              outstream = new ChunkedOutputStream(outstream);
>              }
>          if (this.requestContentLength >= 0) {
> -            instream = new ContentLengthInputStream(instream,  
> this.requestContentLength);
> +            // don't need a watcher here - we're reading from  
> something local,
> +            // not server-side.
> +            instream = new ContentLengthInputStream(instream,  
> this.requestContentLength, null);
>          }
>
>          byte[] tmp = new byte[4096];
> Index: src/test/org/apache/commons/httpclient/TestGetMethodLocal.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/ 
> commons/httpclient/TestGetMethodLocal.java,v
> retrieving revision 1.3
> diff -u -r1.3 TestGetMethodLocal.java
> --- src/test/org/apache/commons/httpclient/TestGetMethodLocal.java	4  
> Feb 2002 15:26:43 -0000	1.3
> +++ src/test/org/apache/commons/httpclient/TestGetMethodLocal.java	4  
> Dec 2002 21:59:43 -0000
> @@ -89,6 +89,7 @@
>      // --------------------------------------------------------------  
> Constants
>
>      private static final String host =  
> System.getProperty("httpclient.test.localHost","127.0.0.1");
> +    private static final String webAppContext =  
> System.getProperty("httpclient.test.webappContext");
>      private static final int port;
>      static {
>          String portString =  
> System.getProperty("httpclient.test.localPort","8080");
> @@ -223,6 +224,40 @@
>          }
>          assertEquals(404,method.getStatusCode());
>
> +    }
> +
> +    /**
> +     * The intent of this test is to allow for the incomplete parsing  
> of a GET
> +     * response, and to make it particularly tricky, the GET response  
> issues
> +     * a Connection: close".
> +     *
> +     * <p>This wants to insure that a recoverable exception is not  
> unexpectedly
> +     * triggered.</p>
> +     */
> +    public void testGetResponseNotReadAutoRecover() {
> +
> +        HttpClient client = new HttpClient();
> +        client.startSession(host, port);
> +
> +        try {
> +            // issue a GET with a connection: close, and don't parse  
> the body.
> +            String path = "/" + webAppContext + "/body";
> +            GetMethod method1 = new GetMethod(path);
> +            method1.addRequestHeader("Connection", "close");
> +            client.executeMethod(method1);
> +            assertEquals(0, method1.getRecoverableExceptionCount() );
> +
> +            // issue another GET.
> +            GetMethod method2 = new GetMethod(path);
> +            client.executeMethod(method2);
> +            assertEquals(0, method2.getRecoverableExceptionCount() );
> +
> +            client.endSession();
> +        }
> +        catch (IOException ioe) {
> +
> +            fail("Problem executing method : " + ioe.toString() );
> +        }
>      }
>
>  }
> Index:  
> src/test/org/apache/commons/httpclient/TestHttpClientLocalHost.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/ 
> commons/httpclient/TestHttpClientLocalHost.java,v
> retrieving revision 1.4
> diff -u -r1.4 TestHttpClientLocalHost.java
> ---  
> src/test/org/apache/commons/httpclient/TestHttpClientLocalHost.java	3  
> Dec 2002 05:46:16 -0000	1.4
> +++  
> src/test/org/apache/commons/httpclient/TestHttpClientLocalHost.java	4  
> Dec 2002 21:59:43 -0000
> @@ -84,6 +84,10 @@
>      // --------------------------------------------------------------  
> Constants
>
>
> +    private static final String host = "127.0.0.1";
> +    private static final int port = 8080;
> +    private static final String webAppContext =  
> System.getProperty("httpclient.test.webappContext");
> +
>      // ------------------------------------------------------------  
> Constructor
>
>
> @@ -100,12 +104,12 @@
>      }
>
>      private HttpClient client = null;
> +    private String  getPath = null;
>      private GetMethod getSlash = null;
> -    private GetMethod getSlash2 = null;
>
>      public void setUp() {
> +        getPath =  "/" + webAppContext + "/body";
>          client = new HttpClient();
> -        getSlash = new GetMethod("/");
>      }
>
>      public void tearDown() {
> @@ -115,6 +119,7 @@
>
>      public void testExecuteMethod() throws Exception {
>          client.startSession(host, port);
> +        GetMethod getSlash = new GetMethod(getPath);
>          assertEquals(200, client.executeMethod(getSlash));
>          String data = getSlash.getResponseBodyAsString();
>          assertTrue(null != data);
> @@ -125,13 +130,14 @@
>
>      public void testExecuteMultipleMethods() throws Exception {
>          client.startSession(host, port);
> +        getSlash = new GetMethod(getPath);
>          for(int i=0;i<10;i++) {
>              assertEquals(200, client.executeMethod(getSlash));
>              String data = getSlash.getResponseBodyAsString();
>              assertTrue(null != data);
>              assertTrue(data.length() > 0);
>              getSlash.recycle();
> -            getSlash.setPath("/");
> +            getSlash.setPath(getPath);
>          }
>          client.endSession();
>      }
> Index: src/test/org/apache/commons/httpclient/TestMethodsLocalHost.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/ 
> commons/httpclient/TestMethodsLocalHost.java,v
> retrieving revision 1.4
> diff -u -r1.4 TestMethodsLocalHost.java
> --- src/test/org/apache/commons/httpclient/TestMethodsLocalHost.java	1  
> Nov 2002 09:51:05 -0000	1.4
> +++ src/test/org/apache/commons/httpclient/TestMethodsLocalHost.java	4  
> Dec 2002 21:59:43 -0000
> @@ -88,6 +88,7 @@
>      // --------------------------------------------------------------  
> Constants
>
>
> +    private static final String webAppContext =  
> System.getProperty("httpclient.test.webappContext");
>      private static final String host = "127.0.0.1";
>      private static final int port = 8080;
>
> @@ -208,7 +209,8 @@
>              fail("Unable to execute method : " + t.toString());
>          }
>
> -        HeadMethod method = new HeadMethod("/");
> +        String path = "/" + webAppContext + "/body";
> +        HeadMethod method = new HeadMethod(path);
>
>          try {
>              client.executeMethod(method);
> @@ -220,7 +222,7 @@
>          assertEquals(200, method.getStatusCode());
>
>          method.recycle();
> -        method.setPath("/index.html");
> +        method.setPath(path);
>
>          try {
>              client.executeMethod(method);
> Index: src/test/org/apache/commons/httpclient/TestMethodsNoHost.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/ 
> commons/httpclient/TestMethodsNoHost.java,v
> retrieving revision 1.11
> diff -u -r1.11 TestMethodsNoHost.java
> --- src/test/org/apache/commons/httpclient/TestMethodsNoHost.java	31  
> Oct 2002 07:45:35 -0000	1.11
> +++ src/test/org/apache/commons/httpclient/TestMethodsNoHost.java	4  
> Dec 2002 21:59:43 -0000
> @@ -71,6 +71,7 @@
>  import junit.framework.TestSuite;
>  import org.apache.commons.httpclient.methods.GetMethod;
>  import org.apache.commons.httpclient.methods.PostMethod;
> +import org.apache.commons.httpclient.methods.HeadMethod;
>
>  /**
>   * @author Rodney Waldhoff
> @@ -301,6 +302,21 @@
>             assertEquals((int) 'A', c);
>          }
>          assertTrue(!conn.isOpen());
> +
> +        // note - this test is here because the HEAD method handler  
> overrides the
> +        // standard behavior for reading a response body.
> +        HeadMethod headMethod = new HeadMethod("/");
> +
> +        conn.addResponse(headers, "");
> +
> +        try {
> +            headMethod.execute(new HttpState(), conn);
> +            assertFalse( conn.isOpen() );
> +
> +        } catch (Throwable t) {
> +            t.printStackTrace();
> +            fail("Unable to execute method : " + t.toString());
> +        }
>      }
>
>      public void testSetGetQueryString1() {
> Index: src/test/org/apache/commons/httpclient/TestStreams.java
> ===================================================================
> RCS file:  
> /home/cvspublic/jakarta-commons/httpclient/src/test/org/apache/ 
> commons/httpclient/TestStreams.java,v
> retrieving revision 1.5
> diff -u -r1.5 TestStreams.java
> --- src/test/org/apache/commons/httpclient/TestStreams.java	25 Oct  
> 2002 10:15:52 -0000	1.5
> +++ src/test/org/apache/commons/httpclient/TestStreams.java	4 Dec 2002  
> 21:59:44 -0000
> @@ -2,7 +2,6 @@
>  package org.apache.commons.httpclient;
>
>  import java.io.*;
> -import java.util.Map;
>  import junit.framework.*;
>
>  import org.apache.commons.httpclient.methods.GetMethod;
> @@ -20,7 +19,7 @@
>          HttpMethod method = new SimpleHttpMethod();
>
>          //Test for when buffer is larger than chunk size
> -        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(correctInput.getBytes()), method);
> +        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(correctInput.getBytes()), method, null);
>          byte[] buffer = new byte[300];
>          ByteArrayOutputStream out = new ByteArrayOutputStream();
>          int len;
> @@ -36,7 +35,7 @@
>
>
>          //Test for when buffer is smaller than chunk size.
> -        in = new ChunkedInputStream(new  
> ByteArrayInputStream(correctInput.getBytes()), method);
> +        in = new ChunkedInputStream(new  
> ByteArrayInputStream(correctInput.getBytes()), method, null);
>          buffer = new byte[7];
>          out = new ByteArrayOutputStream();
>          while ((len = in.read(buffer)) > 0) {
> @@ -55,7 +54,7 @@
>          String corrupInput =  
> "10;key=\"value\"\r\n123456789012345\r\n5\r\n12345\r\n0\r\nFooter1:  
> abcde\r\nFooter2: fghij\r\n";
>          HttpMethod method = new SimpleHttpMethod();
>
> -        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(corrupInput.getBytes()), method);
> +        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(corrupInput.getBytes()), method, null);
>          byte[] buffer = new byte[300];
>          ByteArrayOutputStream out = new ByteArrayOutputStream();
>          int len;
> @@ -73,7 +72,7 @@
>          String input = "0\r\n";
>          HttpMethod method = new SimpleHttpMethod();
>
> -        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(input.getBytes()), method);
> +        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(input.getBytes()), method, null);
>          byte[] buffer = new byte[300];
>          ByteArrayOutputStream out = new ByteArrayOutputStream();
>          int len;
> @@ -85,7 +84,7 @@
>
>      public void testContentLengthInputStream() throws IOException {
>          String correct = "1234567890123456";
> -        InputStream in = new ContentLengthInputStream(new  
> ByteArrayInputStream(correct.getBytes()), 10);
> +        InputStream in = new ContentLengthInputStream(new  
> ByteArrayInputStream(correct.getBytes()), 10, null);
>          byte[] buffer = new byte[50];
>          int len = in.read(buffer);
>          ByteArrayOutputStream out = new ByteArrayOutputStream();
> @@ -101,7 +100,7 @@
>          out.write(input.getBytes());
>          out.close();
>          buffer.close();
> -        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(buffer.toByteArray()), new GetMethod());
> +        InputStream in = new ChunkedInputStream(new  
> ByteArrayInputStream(buffer.toByteArray()), new GetMethod(), null);
>
>          byte[] d = new byte[10];
>          ByteArrayOutputStream result = new ByteArrayOutputStream();
> /*
>  * $Header: $
>  * $Revision: $
>  * $Date: $
>  * ====================================================================
>  *
>  * The Apache Software License, Version 1.1
>  *
>  * Copyright (c) 1999-2002 The Apache Software Foundation.  All rights
>  * reserved.
>  *
>  * Redistribution and use in source and binary forms, with or without
>  * modification, are permitted provided that the following conditions
>  * are met:
>  *
>  * 1. Redistributions of source code must retain the above copyright
>  *    notice, this list of conditions and the following disclaimer.
>  *
>  * 2. Redistributions in binary form must reproduce the above copyright
>  *    notice, this list of conditions and the following disclaimer in
>  *    the documentation and/or other materials provided with the
>  *    distribution.
>  *
>  * 3. The end-user documentation included with the redistribution, if
>  *    any, must include the following acknowlegement:
>  *       "This product includes software developed by the
>  *        Apache Software Foundation (http://www.apache.org/)."
>  *    Alternately, this acknowlegement may appear in the software  
> itself,
>  *    if and wherever such third-party acknowlegements normally appear.
>  *
>  * 4. The names "The Jakarta Project", "HttpClient", and "Apache  
> Software
>  *    Foundation" must not be used to endorse or promote products  
> derived
>  *    from this software without prior written permission. For written
>  *    permission, please contact apache@apache.org.
>  *
>  * 5. Products derived from this software may not be called "Apache"
>  *    nor may "Apache" appear in their names without prior written
>  *    permission of the Apache Group.
>  *
>  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
>  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
>  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
>  * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
>  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
>  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
>  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
>  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
>  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
>  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
>  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
>  * SUCH DAMAGE.
>  * ====================================================================
>  *
>  * This software consists of voluntary contributions made by many
>  * individuals on behalf of the Apache Software Foundation.  For more
>  * information on the Apache Software Foundation, please see
>  * <http://www.apache.org/>.
>  *
>  * [Additional notices, if required by prior licensing conditions]
>  *
>  */
>
> package org.apache.commons.httpclient;
>
> /**
>  * When a response stream has been consumed, various parts of the  
> HttpClient
>  * implementation need to respond appropriately.
>  *
>  * <p>When one of the three types of {@link java.io.InputStream}, one  
> of
>  * AutoCloseInputStream (package), {@link ContentLengthInputStream}, or
>  * {@link ChunkedInputStream} finishes with its content, either because
>  * all content has been consumed, or because it was explicitly closed,
>  * it notifies its corresponding method via this interface.</p>
>  *
>  * @see ContentLengthInputStream
>  * @see ChunkedInputStream
>  */
> public interface ResponseConsumedWatcher {
>
>     /**
>      * A response has been consumed.
>      *
>      * The watcher can release the connection as appropriate.  Note  
> that watchers
>      * should be prepared to receive multiple notifications that a  
> response has
>      * been consumed.  This case might arise in the case of a  
> "Connection: close"
>      * header on a response.
>      */
>     void responseConsumed();
> }
>
> --
> To unsubscribe, e-mail:    
> <mailto:commons-httpclient-dev-unsubscribe@jakarta.apache.org>
> For additional commands, e-mail:  
> <mailto:commons-httpclient-dev-help@jakarta.apache.org>

Mime
View raw message