You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@hc.apache.org by Eric Johnson <er...@tibco.com> on 2002/12/04 23:44:16 UTC

[PATCH] close on input streams handled properly - redux

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


Re: [PATCH] close on input streams handled properly - redux

Posted by Ortwin Glück <or...@nose.ch>.
AutoCloseInputStream should be renamed to WatchedInputStream or 
something similar to reflect its new functionality.

Why duplicate the Watcher functionality in ChunkedInputStream again? I 
would rather wrap the ChunkedInputStream around a AutoCloseInputStream.


Re: [PATCH] close on input streams handled properly - redux

Posted by Michael Becke <be...@u.washington.edu>.
On Thursday, December 5, 2002, at 10:06 AM, Eric Johnson wrote:
> Can someone correct me if I'm wrong, but shouldn't this response be 
> wrapped in an AutoCloseInputStream?  That is to say, the only way to 
> determine the end of the response is to look for the last sent data, 
> which will occur only when the socket is being closed, and thus the 
> connection should be closed?
>
> On further review, I suspect my latest changes will handle proxied 
> connections incorrectly.  Alas, there are apparently no test cases for 
> these, and I am unfamiliar with how they work.  If I read section 5.3 
> of RFC 2817 correctly, and understand all else properly the above code 
> should change to this:
>
>        } else if(canResponseHaveBody(statusLine.getStatusCode()) ) {
>            if (getName().equals(ConnectMethod.NAME) ) {
>                // this stream will not be used by the ConnectMethod, 
> but returning non-null
>                // insures that the connection will not be released to 
> the connection manager
>                // when execute finishes, and the subsequent call to 
> execute on the proxied
>                // method will work on the same connection.
>                return is;
>            }
>            else {
>                // when the end of the stream is encountered, the 
> connection should be closed.
>                return new AutoCloseInputStream(is, m_responseWatcher);
>            }
>        }

The auto close case is handed lower down at:

	if (shouldCloseConnection()) {
             result = new AutoCloseInputStream(result, conn);
         }

I think what we're looking for here is the "auto release" case.  
Something needs to release the connection after the end of the response 
stream has been read.

As far as the proxy goes, it should be okay.  The releasing/closing 
should get handled when the connect method calls execute on the "real" 
http method.

> It is, of course, a tad ugly that there should be method specific code 
> here.  I would hope, rather, that the ConnectMethod() would override a 
> well documented function to do the right thing.  Perhaps something for 
> another day, or for someone else!
>
> Have I got everything?
>
> I apologize to the group that my submissions have been less than 
> perfect, and appreciate your assistance in helping me work through 
> these issues.

Not to worry.  This problem is a real pain, and I certainly don't have 
the perfect solution either:)

Mike


Re: [PATCH] close on input streams handled properly - redux

Posted by Eric Johnson <er...@tibco.com>.
Ryan Hoegg wrote:
Small informal spelling patch:

- insureConnectionRelease()
+ ensureConnectionRelease()

:)

----------------------------

I hate it when I do that!  I have adjusted the spelling, insure --> ensure.

Michael Becke wrote:
I believe it is possible for _readResponseBody() to return a non null 
stream that is just the socket inputStream without any wrappers.  Below 
is the case I'm referring to:

        } else if(canResponseHaveBody(statusLine.getStatusCode())
                && !getName().equals(ConnectMethod.NAME)){
            result = is;
        }

---------------------------

Can someone correct me if I'm wrong, but shouldn't this response be 
wrapped in an AutoCloseInputStream?  That is to say, the only way to 
determine the end of the response is to look for the last sent data, 
which will occur only when the socket is being closed, and thus the 
connection should be closed?

On further review, I suspect my latest changes will handle proxied 
connections incorrectly.  Alas, there are apparently no test cases for 
these, and I am unfamiliar with how they work.  If I read section 5.3 of 
RFC 2817 correctly, and understand all else properly the above code 
should change to this:

        } else if(canResponseHaveBody(statusLine.getStatusCode()) ) {
            if (getName().equals(ConnectMethod.NAME) ) {
                // this stream will not be used by the ConnectMethod, 
but returning non-null
                // insures that the connection will not be released to 
the connection manager
                // when execute finishes, and the subsequent call to 
execute on the proxied
                // method will work on the same connection.
                return is;
            }
            else {
                // when the end of the stream is encountered, the 
connection should be closed.
                return new AutoCloseInputStream(is, m_responseWatcher);
            }
        }

It is, of course, a tad ugly that there should be method specific code 
here.  I would hope, rather, that the ConnectMethod() would override a 
well documented function to do the right thing.  Perhaps something for 
another day, or for someone else!

Have I got everything?

I apologize to the group that my submissions have been less than 
perfect, and appreciate your assistance in helping me work through these 
issues.

-Eric.


Re: [PATCH] close on input streams handled properly - redux

Posted by Ryan Hoegg <rh...@isisnetworks.net>.
Small informal spelling patch:

- insureConnectionRelease()
+ ensureConnectionRelease()

:)

--
Ryan Hoegg
ISIS Networks
http://www.isisnetworks.net

Eric Johnson wrote:

> ...
>    * after a bunch of returns, execute() finally checks the
>      doneWithConnection flag, and calls insureConnectionRelease(),
>      which calls releaseConnection() as you would hope.
>
> Hope that helps clarify what is happening.
>
> -Eric




Re: [PATCH] close on input streams handled properly - redux

Posted by Michael Becke <be...@u.washington.edu>.
I believe it is possible for _readResponseBody() to return a non null 
stream that is just the socket inputStream without any wrappers.  Below 
is the case I'm referring to:

         } else if(canResponseHaveBody(statusLine.getStatusCode())
                 && !getName().equals(ConnectMethod.NAME)){
             result = is;
         }

Mike

On Wednesday, December 4, 2002, at 06:32 PM, Eric Johnson wrote:

> Michael,
>
> Good question!  Yes, this case is covered.  What happens in 
> HttpMethodBase:
>
>    * readResponseBody() sets doneWithConnection to false, and calls
>      _readResponseBody().
>    * _readResponseBody() returns a "null" stream in the case you
>      mention.  As a result, "finishWithConnection()" gets called
>    * finishWithConnection() closes the connection if it "should close"
>      ("Connection: close" header), and also sets the doneWithConnection
>      flag.
>    * after a bunch of returns, execute() finally checks the
>      doneWithConnection flag, and calls insureConnectionRelease(),
>      which calls releaseConnection() as you would hope.
>
> Hope that helps clarify what is happening.
>
> -Eric
>
> Michael Becke wrote:
>
>> 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
>>
>>
>
>
> --
> To unsubscribe, e-mail:   
> <ma...@jakarta.apache.org>
> For additional commands, e-mail: 
> <ma...@jakarta.apache.org>
>


Re: [PATCH] close on input streams handled properly - redux

Posted by Eric Johnson <er...@tibco.com>.
Michael,

Good question!  Yes, this case is covered.  What happens in HttpMethodBase:

    * readResponseBody() sets doneWithConnection to false, and calls
      _readResponseBody().
    * _readResponseBody() returns a "null" stream in the case you
      mention.  As a result, "finishWithConnection()" gets called
    * finishWithConnection() closes the connection if it "should close"
      ("Connection: close" header), and also sets the doneWithConnection
      flag.
    * after a bunch of returns, execute() finally checks the
      doneWithConnection flag, and calls insureConnectionRelease(),
      which calls releaseConnection() as you would hope.

Hope that helps clarify what is happening.

-Eric

Michael Becke wrote:

> 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
>
>


Re: [PATCH] close on input streams handled properly - redux

Posted by Michael Becke <be...@u.washington.edu>.
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:    
> <ma...@jakarta.apache.org>
> For additional commands, e-mail:  
> <ma...@jakarta.apache.org>