View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.159.2.33 2004/10/10 00:00:35 mbecke Exp $
3    * $Revision: 1.159.2.33 $
4    * $Date: 2004/10/10 00:00:35 $
5    *
6    * ====================================================================
7    *
8    *  Copyright 1999-2004 The Apache Software Foundation
9    *
10   *  Licensed under the Apache License, Version 2.0 (the "License");
11   *  you may not use this file except in compliance with the License.
12   *  You may obtain a copy of the License at
13   *
14   *      http://www.apache.org/licenses/LICENSE-2.0
15   *
16   *  Unless required by applicable law or agreed to in writing, software
17   *  distributed under the License is distributed on an "AS IS" BASIS,
18   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19   *  See the License for the specific language governing permissions and
20   *  limitations under the License.
21   * ====================================================================
22   *
23   * This software consists of voluntary contributions made by many
24   * individuals on behalf of the Apache Software Foundation.  For more
25   * information on the Apache Software Foundation, please see
26   * <http://www.apache.org/>.
27   *
28   * [Additional notices, if required by prior licensing conditions]
29   *
30   */
31  
32  package org.apache.commons.httpclient;
33  
34  import java.io.ByteArrayInputStream;
35  import java.io.ByteArrayOutputStream;
36  import java.io.IOException;
37  import java.io.InputStream;
38  import java.io.InterruptedIOException;
39  import java.util.HashSet;
40  import java.util.Set;
41  
42  import org.apache.commons.httpclient.auth.AuthScheme;
43  import org.apache.commons.httpclient.auth.AuthenticationException;
44  import org.apache.commons.httpclient.auth.HttpAuthenticator;
45  import org.apache.commons.httpclient.auth.MalformedChallengeException;
46  import org.apache.commons.httpclient.auth.NTLMScheme;
47  import org.apache.commons.httpclient.cookie.CookiePolicy;
48  import org.apache.commons.httpclient.cookie.CookieSpec;
49  import org.apache.commons.httpclient.cookie.MalformedCookieException;
50  import org.apache.commons.httpclient.protocol.Protocol;
51  import org.apache.commons.httpclient.util.EncodingUtil;
52  import org.apache.commons.logging.Log;
53  import org.apache.commons.logging.LogFactory;
54  
55  /***
56   * An abstract base implementation of HttpMethod.
57   * <p>
58   * At minimum, subclasses will need to override:
59   * <ul>
60   *   <li>{@link #getName} to return the approriate name for this method
61   *   </li>
62   * </ul>
63   *
64   * <p>
65   * When a method's request may contain a body, subclasses will typically want
66   * to override:
67   * <ul>
68   *   <li>{@link #getRequestContentLength} to indicate the length (in bytes)
69   *     of that body</li>
70   *   <li>{@link #writeRequestBody writeRequestBody(HttpState,HttpConnection)}
71   *     to write the body</li>
72   * </ul>
73   * </p>
74   *
75   * <p>
76   * When a method requires additional request headers, subclasses will typically
77   * want to override:
78   * <ul>
79   *   <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
80   *      to write those headers
81   *   </li>
82   * </ul>
83   * </p>
84   *
85   * <p>
86   * When a method expects specific response headers, subclasses may want to
87   * override:
88   * <ul>
89   *   <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}
90   *     to handle those headers
91   *   </li>
92   * </ul>
93   * </p>
94   *
95   *
96   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
97   * @author Rodney Waldhoff
98   * @author Sean C. Sullivan
99   * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
100  * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
101  * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
102  * @author Ortwin Glueck
103  * @author Eric Johnson
104  * @author Michael Becke
105  * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
106  * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
107  * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
108  *
109  * @version $Revision: 1.159.2.33 $ $Date: 2004/10/10 00:00:35 $
110  */
111 public abstract class HttpMethodBase implements HttpMethod {
112 
113     /*** Maximum number of redirects and authentications that will be followed */
114     private static final int MAX_FORWARDS = 100;
115 
116     // -------------------------------------------------------------- Constants
117 
118     /*** Log object for this class. */
119     private static final Log LOG = LogFactory.getLog(HttpMethodBase.class);
120 
121     /*** The User-Agent header sent on every request. */
122     protected static final Header USER_AGENT;
123 
124     static {
125         String agent = null;
126         try {
127             agent = System.getProperty("httpclient.useragent");
128         } catch (SecurityException ignore) {
129         }
130         if (agent == null) {
131             agent = "Jakarta Commons-HttpClient/2.0.2";
132         }
133         USER_AGENT = new Header("User-Agent", agent);
134     }
135 
136     // ----------------------------------------------------- Instance variables 
137 
138     /*** Request headers, if any. */
139     private HeaderGroup requestHeaders = new HeaderGroup();
140 
141     /*** The Status-Line from the response. */
142     private StatusLine statusLine = null;
143 
144     /*** Response headers, if any. */
145     private HeaderGroup responseHeaders = new HeaderGroup();
146 
147     /*** Response trailer headers, if any. */
148     private HeaderGroup responseTrailerHeaders = new HeaderGroup();
149 
150     /*** Authentication scheme used to authenticate againt the target server */
151     private AuthScheme authScheme = null;
152 
153     /*** Realms this method tried to authenticate to */
154     private Set realms = null;
155 
156     /*** Actual authentication realm */
157     private String realm = null;
158 
159     /*** Authentication scheme used to authenticate againt the proxy server */
160     private AuthScheme proxyAuthScheme = null;
161 
162     /*** Proxy Realms this method tried to authenticate to */
163     private Set proxyRealms = null;
164 
165     /*** Actual proxy authentication realm */
166     private String proxyRealm = null;
167 
168     /*** Path of the HTTP method. */
169     private String path = null;
170 
171     /*** Query string of the HTTP method, if any. */
172     private String queryString = null;
173 
174     /*** The response body of the HTTP method, assuming it has not be 
175      * intercepted by a sub-class. */
176     private InputStream responseStream = null;
177 
178     /*** The connection that the response stream was read from. */
179     private HttpConnection responseConnection = null;
180 
181     /*** Buffer for the response */
182     private byte[] responseBody = null;
183 
184     /*** True if the HTTP method should automatically follow
185      *  HTTP redirects. */
186     private boolean followRedirects = false;
187 
188     /*** True if the HTTP method should automatically handle
189      *  HTTP authentication challenges. */
190     private boolean doAuthentication = true;
191 
192     /*** True if version 1.1 of the HTTP protocol should be used per default. */
193     private boolean http11 = true;
194 
195     /*** True if this HTTP method should strictly follow the HTTP protocol
196      * specification. */
197     private boolean strictMode = false;
198 
199     /*** True if this method has already been executed. */
200     private boolean used = false;
201 
202     /*** Count of how many times did this HTTP method transparently handle 
203      * a recoverable exception. */
204     private int recoverableExceptionCount = 0;
205 
206     /*** The host configuration for this HTTP method, can be null */
207     private HostConfiguration hostConfiguration;
208 
209     /***
210      * Handles method retries
211      */
212     private MethodRetryHandler methodRetryHandler;
213 
214     /*** True if this method is currently being executed. */
215     private boolean inExecute = false;
216 
217     /*** True if this HTTP method is finished with the connection */
218     private boolean doneWithConnection = false;
219 
220     /*** True if the connection must be closed when no longer needed */
221     private boolean connectionCloseForced = false;
222 
223     /*** Number of milliseconds to wait for 100-contunue response. */
224     private static final int RESPONSE_WAIT_TIME_MS = 3000;
225 
226     /*** Maximum buffered response size (in bytes) that triggers no warning. */
227     private static final int BUFFER_WARN_TRIGGER_LIMIT = 1024*1024; //1 MB
228 
229     /*** Default initial size of the response buffer if content length is unknown. */
230     private static final int DEFAULT_INITIAL_BUFFER_SIZE = 4*1024; // 4 kB
231 
232     // ----------------------------------------------------------- Constructors
233 
234     /***
235      * No-arg constructor.
236      */
237     public HttpMethodBase() {
238     }
239 
240     /***
241      * Constructor specifying a URI.
242      * It is responsibility of the caller to ensure that URI elements
243      * (path & query parameters) are properly encoded (URL safe).
244      *
245      * @param uri either an absolute or relative URI. The URI is expected
246      *            to be URL-encoded
247      * 
248      * @throws IllegalArgumentException when URI is invalid
249      * @throws IllegalStateException when protocol of the absolute URI is not recognised
250      */
251     public HttpMethodBase(String uri) 
252         throws IllegalArgumentException, IllegalStateException {
253 
254         try {
255 
256             // create a URI and allow for null/empty uri values
257             if (uri == null || uri.equals("")) {
258                 uri = "/";
259             }
260             URI parsedURI = new URI(uri.toCharArray());
261             
262             // only set the host if specified by the URI
263             if (parsedURI.isAbsoluteURI()) {
264                 hostConfiguration = new HostConfiguration();
265                 hostConfiguration.setHost(
266                     parsedURI.getHost(),
267                     parsedURI.getPort(),
268                     parsedURI.getScheme()
269                 ); 
270             }
271             
272             // set the path, defaulting to root
273             setPath(
274                 parsedURI.getPath() == null
275                 ? "/"
276                 : parsedURI.getEscapedPath()
277             );
278             setQueryString(parsedURI.getEscapedQuery());
279 
280         } catch (URIException e) {
281             throw new IllegalArgumentException("Invalid uri '" 
282                 + uri + "': " + e.getMessage() 
283             );
284         }
285     }
286 
287     // ------------------------------------------- Property Setters and Getters
288 
289     /***
290      * Obtains the name of the HTTP method as used in the HTTP request line,
291      * for example <tt>"GET"</tt> or <tt>"POST"</tt>.
292      * 
293      * @return the name of this method
294      */
295     public abstract String getName();
296 
297     /***
298      * Returns the URI of the HTTP method
299      * 
300      * @return The URI
301      * 
302      * @throws URIException If the URI cannot be created.
303      * 
304      * @see org.apache.commons.httpclient.HttpMethod#getURI()
305      */
306     public URI getURI() throws URIException {
307 
308         if (hostConfiguration == null) {
309             // just use a relative URI, the host hasn't been set
310             URI tmpUri = new URI(null, null, path, null, null);
311             tmpUri.setEscapedQuery(queryString);
312             return tmpUri;
313         } else {
314 
315             // we only want to include the port if it's not the default
316             int port = hostConfiguration.getPort();
317             if (port == hostConfiguration.getProtocol().getDefaultPort()) {
318                 port = -1;
319             }
320 
321             URI tmpUri = new URI(
322                 hostConfiguration.getProtocol().getScheme(),
323                 null,
324                 hostConfiguration.getHost(),
325                 port,
326                 path,
327                 null // to set an escaped form
328             );
329             tmpUri.setEscapedQuery(queryString);
330             return tmpUri;
331 
332         }
333 
334     }
335 
336     /***
337      * Sets whether or not the HTTP method should automatically follow HTTP redirects 
338      * (status code 302, etc.)
339      * 
340      * @param followRedirects <tt>true</tt> if the method will automatically follow redirects,
341      * <tt>false</tt> otherwise.
342      */
343     public void setFollowRedirects(boolean followRedirects) {
344         this.followRedirects = followRedirects;
345     }
346 
347     /***
348      * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects 
349      * (status code 302, etc.), <tt>false</tt> otherwise.
350      * 
351      * @return <tt>true</tt> if the method will automatically follow HTTP redirects, 
352      * <tt>false</tt> otherwise.
353      */
354     public boolean getFollowRedirects() {
355         return this.followRedirects;
356     }
357 
358     /***
359     /** Sets whether version 1.1 of the HTTP protocol should be used per default.
360      *
361      * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
362      */
363     public void setHttp11(boolean http11) {
364         this.http11 = http11;
365     }
366 
367     /***
368      * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP 
369      * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise
370      *
371      * @return <tt>true</tt> if authentication challenges will be processed 
372      * automatically, <tt>false</tt> otherwise.
373      * 
374      * @since 2.0
375      */
376     public boolean getDoAuthentication() {
377         return doAuthentication;
378     }
379 
380     /***
381      * Sets whether or not the HTTP method should automatically handle HTTP 
382      * authentication challenges (status code 401, etc.)
383      *
384      * @param doAuthentication <tt>true</tt> to process authentication challenges
385      * authomatically, <tt>false</tt> otherwise.
386      * 
387      * @since 2.0
388      */
389     public void setDoAuthentication(boolean doAuthentication) {
390         this.doAuthentication = doAuthentication;
391     }
392 
393     // ---------------------------------------------- Protected Utility Methods
394 
395     /***
396      * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be 
397      * used per default, <tt>false</tt> if version 1.0 should be used.
398      *
399      * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
400      */
401     public boolean isHttp11() {
402         return http11;
403     }
404 
405     /***
406      * Sets the path of the HTTP method.
407      * It is responsibility of the caller to ensure that the path is
408      * properly encoded (URL safe).
409      *
410      * @param path the path of the HTTP method. The path is expected
411      *        to be URL-encoded
412      */
413     public void setPath(String path) {
414         this.path = path;
415     }
416 
417     /***
418      * Adds the specified request header, NOT overwriting any previous value.
419      * Note that header-name matching is case insensitive.
420      *
421      * @param header the header to add to the request
422      */
423     public void addRequestHeader(Header header) {
424         LOG.trace("HttpMethodBase.addRequestHeader(Header)");
425 
426         if (header == null) {
427             LOG.debug("null header value ignored");
428         } else {
429             getRequestHeaderGroup().addHeader(header);
430         }
431     }
432 
433     /***
434      * Use this method internally to add footers.
435      * 
436      * @param footer The footer to add.
437      */
438     public void addResponseFooter(Header footer) {
439         getResponseTrailerHeaderGroup().addHeader(footer);
440     }
441 
442     /***
443      * Gets the path of this HTTP method.
444      * Calling this method <em>after</em> the request has been executed will 
445      * return the <em>actual</em> path, following any redirects automatically
446      * handled by this HTTP method.
447      *
448      * @return the path to request or "/" if the path is blank.
449      */
450     public String getPath() {
451         return (path == null || path.equals("")) ? "/" : path;
452     }
453 
454     /***
455      * Sets the query string of this HTTP method. The caller must ensure that the string 
456      * is properly URL encoded. The query string should not start with the question 
457      * mark character.
458      *
459      * @param queryString the query string
460      * 
461      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
462      */
463     public void setQueryString(String queryString) {
464         this.queryString = queryString;
465     }
466 
467     /***
468      * Sets the query string of this HTTP method.  The pairs are encoded as UTF-8 characters.  
469      * To use a different charset the parameters can be encoded manually using EncodingUtil 
470      * and set as a single String.
471      *
472      * @param params an array of {@link NameValuePair}s to add as query string
473      *        parameters. The name/value pairs will be automcatically 
474      *        URL encoded
475      * 
476      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
477      * @see #setQueryString(String)
478      */
479     public void setQueryString(NameValuePair[] params) {
480         LOG.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
481         queryString = EncodingUtil.formUrlEncode(params, "UTF-8");
482     }
483 
484     /***
485      * Gets the query string of this HTTP method.
486      *
487      * @return The query string
488      */
489     public String getQueryString() {
490         return queryString;
491     }
492 
493     /***
494      * Set the specified request header, overwriting any previous value. Note
495      * that header-name matching is case-insensitive.
496      *
497      * @param headerName the header's name
498      * @param headerValue the header's value
499      */
500     public void setRequestHeader(String headerName, String headerValue) {
501         Header header = new Header(headerName, headerValue);
502         setRequestHeader(header);
503     }
504 
505     /***
506      * Sets the specified request header, overwriting any previous value.
507      * Note that header-name matching is case insensitive.
508      * 
509      * @param header the header
510      */
511     public void setRequestHeader(Header header) {
512         
513         Header[] headers = getRequestHeaderGroup().getHeaders(header.getName());
514         
515         for (int i = 0; i < headers.length; i++) {
516             getRequestHeaderGroup().removeHeader(headers[i]);
517         }
518         
519         getRequestHeaderGroup().addHeader(header);
520         
521     }
522 
523     /***
524      * Returns the specified request header. Note that header-name matching is
525      * case insensitive. <tt>null</tt> will be returned if either
526      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
527      * <i>headerName</i>.
528      * 
529      * @param headerName The name of the header to be returned.
530      *
531      * @return The specified request header.
532      */
533     public Header getRequestHeader(String headerName) {
534         if (headerName == null) {
535             return null;
536         } else {
537             return getRequestHeaderGroup().getCondensedHeader(headerName);
538         }
539     }
540 
541     /***
542      * Returns an array of the requests headers that the HTTP method currently has
543      *
544      * @return an array of my request headers.
545      */
546     public Header[] getRequestHeaders() {
547         return getRequestHeaderGroup().getAllHeaders();
548     }
549 
550     /***
551      * Gets the {@link HeaderGroup header group} storing the request headers.
552      * 
553      * @return a HeaderGroup
554      * 
555      * @since 2.0beta1
556      */
557     protected HeaderGroup getRequestHeaderGroup() {
558         return requestHeaders;
559     }
560 
561     /***
562      * Gets the {@link HeaderGroup header group} storing the response trailer headers 
563      * as per RFC 2616 section 3.6.1.
564      * 
565      * @return a HeaderGroup
566      * 
567      * @since 2.0beta1
568      */
569     protected HeaderGroup getResponseTrailerHeaderGroup() {
570         return responseTrailerHeaders;
571     }
572 
573     /***
574      * Gets the {@link HeaderGroup header group} storing the response headers.
575      * 
576      * @return a HeaderGroup
577      * 
578      * @since 2.0beta1
579      */
580     protected HeaderGroup getResponseHeaderGroup() {
581         return responseHeaders;
582     }
583     
584     /***
585      * Returns the response status code.
586      *
587      * @return the status code associated with the latest response.
588      */
589     public int getStatusCode() {
590         return statusLine.getStatusCode();
591     }
592 
593     /***
594      * Provides access to the response status line.
595      *
596      * @return the status line object from the latest response.
597      * @since 2.0
598      */
599     public StatusLine getStatusLine() {
600         return statusLine;
601     }
602 
603     /***
604      * Checks if response data is available.
605      * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise.
606      */
607     private boolean responseAvailable() {
608         return (responseBody != null) || (responseStream != null);
609     }
610 
611     /***
612      * Returns an array of the response headers that the HTTP method currently has
613      * in the order in which they were read.
614      *
615      * @return an array of response headers.
616      */
617     public Header[] getResponseHeaders() {
618         return getResponseHeaderGroup().getAllHeaders();
619     }
620 
621     /***
622      * Gets the response header associated with the given name. Header name
623      * matching is case insensitive. <tt>null</tt> will be returned if either
624      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
625      * <i>headerName</i>.
626      *
627      * @param headerName the header name to match
628      *
629      * @return the matching header
630      */
631     public Header getResponseHeader(String headerName) {        
632         if (headerName == null) {
633             return null;
634         } else {
635             return getResponseHeaderGroup().getCondensedHeader(headerName);
636         }        
637     }
638 
639 
640     /***
641      * Return the length (in bytes) of the response body, as specified in a
642      * <tt>Content-Length</tt> header.
643      *
644      * <p>
645      * Return <tt>-1</tt> when the content-length is unknown.
646      * </p>
647      *
648      * @return content length, if <tt>Content-Length</tt> header is available. 
649      *          <tt>0</tt> indicates that the request has no body.
650      *          If <tt>Content-Length</tt> header is not present, the method 
651      *          returns  <tt>-1</tt>.
652      */
653     protected int getResponseContentLength() {
654         Header[] headers = getResponseHeaderGroup().getHeaders("Content-Length");
655         if (headers.length == 0) {
656             return -1;
657         }
658         if (headers.length > 1) {
659             LOG.warn("Multiple content-length headers detected");
660         }
661         for (int i = headers.length - 1; i >= 0; i--) {
662             Header header = headers[i];
663             try {
664                 return Integer.parseInt(header.getValue());
665             } catch (NumberFormatException e) {
666                 if (LOG.isWarnEnabled()) {
667                     LOG.warn("Invalid content-length value: " + e.getMessage());
668                 }
669             }
670             // See if we can have better luck with another header, if present
671         }
672         return -1;
673     }
674 
675 
676     /***
677      * Returns the response body of the HTTP method, if any, as an array of bytes.
678      * If response body is not available or cannot be read, returns <tt>null</tt>.
679      * 
680      * Note: This will cause the entire response body to be buffered in memory. A
681      * malicious server may easily exhaust all the VM memory. It is strongly
682      * recommended, to use getResponseAsStream if the content length of the response
683      * is unknown or resonably large.
684      * 
685      * @return The response body.
686      */
687     public byte[] getResponseBody() {
688         if (this.responseBody == null) {
689             try {
690                 InputStream instream = getResponseBodyAsStream();
691                 if (instream != null) {
692                     int contentLength = getResponseContentLength();
693                     if ((contentLength == -1) || (contentLength > BUFFER_WARN_TRIGGER_LIMIT)) {
694                         LOG.warn("Going to buffer response body of large or unknown size. "
695                                 +"Using getResponseAsStream instead is recommended.");
696                     }
697                     LOG.debug("Buffering response body");
698                     ByteArrayOutputStream outstream = new ByteArrayOutputStream(
699                             contentLength > 0 ? contentLength : DEFAULT_INITIAL_BUFFER_SIZE);
700                     byte[] buffer = new byte[4096];
701                     int len;
702                     while ((len = instream.read(buffer)) > 0) {
703                         outstream.write(buffer, 0, len);
704                     }
705                     outstream.close();
706                     setResponseStream(null);
707                     this.responseBody = outstream.toByteArray();
708                 }
709             } catch (IOException e) {
710                 LOG.error("I/O failure reading response body", e);
711                 this.responseBody = null;
712             }
713         }
714         return this.responseBody;
715     }
716 
717     /***
718      * Returns the response body of the HTTP method, if any, as an {@link InputStream}. 
719      * If response body is not available, returns <tt>null</tt>
720      * 
721      * @return The response body
722      * 
723      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
724      * response body.
725      */
726     public InputStream getResponseBodyAsStream() throws IOException {
727         if (responseStream != null) {
728             return responseStream;
729         }
730         if (responseBody != null) {
731             InputStream byteResponseStream = new ByteArrayInputStream(responseBody);
732             LOG.debug("re-creating response stream from byte array");
733             return byteResponseStream;
734         }
735         return null;
736     }
737 
738     /***
739      * Returns the response body of the HTTP method, if any, as a {@link String}. 
740      * If response body is not available or cannot be read, returns <tt>null</tt>
741      * The string conversion on the data is done using the character encoding specified
742      * in <tt>Content-Type</tt> header.
743      * 
744      * Note: This will cause the entire response body to be buffered in memory. A
745      * malicious server may easily exhaust all the VM memory. It is strongly
746      * recommended, to use getResponseAsStream if the content length of the response
747      * is unknown or resonably large.
748      * 
749      * @return The response body.
750      */
751     public String getResponseBodyAsString() {
752         byte[] rawdata = null;
753         if (responseAvailable()) {
754             rawdata = getResponseBody();
755         }
756         if (rawdata != null) {
757             return HttpConstants.getContentString(rawdata, getResponseCharSet());
758         } else {
759             return null;
760         }
761     }
762 
763     /***
764      * Returns an array of the response footers that the HTTP method currently has
765      * in the order in which they were read.
766      *
767      * @return an array of footers
768      */
769     public Header[] getResponseFooters() {
770         return getResponseTrailerHeaderGroup().getAllHeaders();
771     }
772 
773     /***
774      * Gets the response footer associated with the given name.
775      * Footer name matching is case insensitive.
776      * <tt>null</tt> will be returned if either <i>footerName</i> is
777      * <tt>null</tt> or there is no matching footer for <i>footerName</i>
778      * or there are no footers available.  If there are multiple footers
779      * with the same name, there values will be combined with the ',' separator
780      * as specified by RFC2616.
781      * 
782      * @param footerName the footer name to match
783      * @return the matching footer
784      */
785     public Header getResponseFooter(String footerName) {
786         if (footerName == null) {
787             return null;
788         } else {
789             return getResponseTrailerHeaderGroup().getCondensedHeader(footerName);
790         }
791     }
792 
793     /***
794      * Sets the response stream.
795      * @param responseStream The new response stream.
796      */
797     protected void setResponseStream(InputStream responseStream) {
798         this.responseStream = responseStream;
799     }
800 
801     /***
802      * Returns a stream from which the body of the current response may be read.
803      * If the method has not yet been executed, if <code>responseBodyConsumed</code>
804      * has been called, or if the stream returned by a previous call has been closed,
805      * <code>null</code> will be returned.
806      *
807      * @return the current response stream
808      */
809     protected InputStream getResponseStream() {
810         return responseStream;
811     }
812     
813     /***
814      * Returns the status text (or "reason phrase") associated with the latest
815      * response.
816      * 
817      * @return The status text.
818      */
819     public String getStatusText() {
820         return statusLine.getReasonPhrase();
821     }
822 
823     /***
824      * Defines how strictly HttpClient follows the HTTP protocol specification  
825      * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely
826      * implements the requirements of the specification, whereas in non-strict mode 
827      * it attempts to mimic the exact behaviour of commonly used HTTP agents, 
828      * which many HTTP servers expect.
829      * 
830      * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise
831      */
832     public void setStrictMode(boolean strictMode) {
833         this.strictMode = strictMode;
834     }
835 
836     /***
837      * Returns the value of the strict mode flag.
838      *
839      * @return <tt>true</tt> if strict mode is enabled, <tt>false</tt> otherwise
840      */
841     public boolean isStrictMode() {
842         return strictMode;
843     }
844 
845     /***
846      * Adds the specified request header, NOT overwriting any previous value.
847      * Note that header-name matching is case insensitive.
848      *
849      * @param headerName the header's name
850      * @param headerValue the header's value
851      */
852     public void addRequestHeader(String headerName, String headerValue) {
853         addRequestHeader(new Header(headerName, headerValue));
854     }
855 
856     /***
857      * Tests if the connection should be force-closed when no longer needed.
858      * 
859      * @return <code>true</code> if the connection must be closed
860      */
861     protected boolean isConnectionCloseForced() {
862         return this.connectionCloseForced;
863     }
864 
865     /***
866      * Sets whether or not the connection should be force-closed when no longer 
867      * needed. This value should only be set to <code>true</code> in abnormal 
868      * circumstances, such as HTTP protocol violations. 
869      * 
870      * @param b <code>true</code> if the connection must be closed, <code>false</code>
871      * otherwise.
872      */
873     protected void setConnectionCloseForced(boolean b) {
874         if (LOG.isDebugEnabled()) {
875             LOG.debug("Force-close connection: " + b);
876         }
877         this.connectionCloseForced = b;
878     }
879 
880     /***
881      * Tests if the connection should be closed after the method has been executed.
882      * The connection will be left open when using HTTP/1.1 or if <tt>Connection: 
883      * keep-alive</tt> header was sent.
884      * 
885      * @param conn the connection in question
886      * 
887      * @return boolean true if we should close the connection.
888      */
889     protected boolean shouldCloseConnection(HttpConnection conn) {
890 
891         // Connection must be closed due to an abnormal circumstance 
892         if (isConnectionCloseForced()) {
893             LOG.debug("Should force-close connection.");
894             return true;
895         }
896 
897         Header connectionHeader = null;
898         // In case being connected via a proxy server
899         if (!conn.isTransparent()) {
900             // Check for 'proxy-connection' directive
901             connectionHeader = responseHeaders.getFirstHeader("proxy-connection");
902         }
903         // In all cases Check for 'connection' directive
904         // some non-complaint proxy servers send it instread of
905         // expected 'proxy-connection' directive
906         if (connectionHeader == null) {
907             connectionHeader = responseHeaders.getFirstHeader("connection");
908         }
909         if (connectionHeader != null) {
910             if (connectionHeader.getValue().equalsIgnoreCase("close")) {
911                 if (LOG.isDebugEnabled()) {
912                     LOG.debug("Should close connection in response to " 
913                         + connectionHeader.toExternalForm());
914                 }
915                 return true;
916             } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) {
917                 if (LOG.isDebugEnabled()) {
918                     LOG.debug("Should NOT close connection in response to " 
919                         + connectionHeader.toExternalForm());
920                 }
921                 return false;
922             } else {
923                 if (LOG.isDebugEnabled()) {
924                     LOG.debug("Unknown directive: " + connectionHeader.toExternalForm());
925                 }
926             }
927         }
928         LOG.debug("Resorting to protocol version default close connection policy");
929         // missing or invalid connection header, do the default
930         if (http11) {
931             LOG.debug("Should NOT close connection, using HTTP/1.1.");
932         } else {
933             LOG.debug("Should close connection, using HTTP/1.0.");
934         }
935         return !http11;
936     }
937     
938     /***
939      * Tests if the method needs to be retried.
940      * @param statusCode The status code
941      * @param state the {@link HttpState state} information associated with this method
942      * @param conn the {@link HttpConnection connection} to be used
943      * @return boolean true if a retry is needed.
944      */
945     private boolean isRetryNeeded(int statusCode, HttpState state, HttpConnection conn) {
946         switch (statusCode) {
947             case HttpStatus.SC_UNAUTHORIZED:
948             case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
949                 LOG.debug("Authorization required");
950                 if (doAuthentication) { //process authentication response
951                     //if the authentication is successful, return the statusCode
952                     //otherwise, drop through the switch and try again.
953                     if (processAuthenticationResponse(state, conn)) {
954                         return false;
955                     }
956                 } else { //let the client handle the authenticaiton
957                     return false;
958                 }
959                 break;
960 
961             case HttpStatus.SC_MOVED_TEMPORARILY:
962             case HttpStatus.SC_MOVED_PERMANENTLY:
963             case HttpStatus.SC_SEE_OTHER:
964             case HttpStatus.SC_TEMPORARY_REDIRECT:
965                 LOG.debug("Redirect required");
966 
967                 if (!processRedirectResponse(conn)) {
968                     return false;
969                 }
970                 break;
971 
972             default:
973                 // neither an unauthorized nor a redirect response
974                 return false;
975         } //end of switch
976 
977         return true;
978     }
979 
980     /***
981      * Tests if the this method is ready to be executed.
982      * 
983      * @param state the {@link HttpState state} information associated with this method
984      * @param conn the {@link HttpConnection connection} to be used
985      * @throws HttpException If the method is in invalid state.
986      */
987     private void checkExecuteConditions(HttpState state, HttpConnection conn)
988     throws HttpException {
989 
990         if (state == null) {
991             throw new IllegalArgumentException("HttpState parameter may not be null");
992         }
993         if (conn == null) {
994             throw new IllegalArgumentException("HttpConnection parameter may not be null");
995         }
996         if (hasBeenUsed()) {
997             throw new HttpException("Already used, but not recycled.");
998         }
999         if (!validate()) {
1000             throw new HttpException("Not valid");
1001         }
1002         if (inExecute) {
1003             throw new IllegalStateException("Execute invoked recursively, or exited abnormally.");
1004         }
1005     }
1006 
1007     /***
1008      * Execute this HTTP method. Note that we cannot currently support redirects
1009      * that change  the connection parameters (host, port, protocol) because
1010      * we  don't yet have a good way to get the new connection.  For  the time
1011      * being, we just return the redirect response code,  and allow the user
1012      * agent to resubmit if desired.
1013      *
1014      * @param state {@link HttpState state} information to associate with this
1015      *        request. Must be non-null.
1016      * @param conn the {@link HttpConnection connection} to used to execute
1017      *        this HTTP method. Must be non-null.
1018      *        Note that we cannot currently support redirects that
1019      *        change the HttpConnection parameters (host, port, protocol)
1020      *        because we don't yet have a good way to get the new connection.
1021      *        For the time being, we just return the 302 response, and allow
1022      *        the user agent to resubmit if desired.
1023      *
1024      * @return the integer status code if one was obtained, or <tt>-1</tt>
1025      *
1026      * @throws IOException if an I/O (transport) error occurs
1027      * @throws HttpException  if a protocol exception occurs.
1028      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1029      *                    Usually this kind of exceptions can be recovered from by
1030      *                    retrying the HTTP method 
1031      */
1032     public int execute(HttpState state, HttpConnection conn)
1033         throws HttpException, HttpRecoverableException, 
1034             IOException {
1035                 
1036         LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
1037 
1038         // this is our connection now, assign it to a local variable so 
1039         // that it can be released later
1040         this.responseConnection = conn;
1041 
1042         checkExecuteConditions(state, conn);
1043         inExecute = true;
1044 
1045         try {
1046             //pre-emptively add the authorization header, if required.
1047             if (state.isAuthenticationPreemptive()) {
1048 
1049                 LOG.debug("Preemptively sending default basic credentials");
1050 
1051                 try {
1052                     if (HttpAuthenticator.authenticateDefault(this, conn, state)) {
1053                         LOG.debug("Default basic credentials applied");
1054                     } else {
1055                         LOG.warn("Preemptive authentication failed");
1056                     }
1057                     if (conn.isProxied()) {
1058                         if (HttpAuthenticator.authenticateProxyDefault(this, conn, state)) {
1059                             LOG.debug("Default basic proxy credentials applied");
1060                         } else {
1061                             LOG.warn("Preemptive proxy authentication failed");
1062                         }
1063                     }
1064                 } catch (AuthenticationException e) {
1065                     // Log error and move on
1066                     LOG.error(e.getMessage(), e);
1067                 }
1068             }
1069 
1070             realms = new HashSet();
1071             proxyRealms = new HashSet();
1072             int forwardCount = 0; //protect from an infinite loop
1073 
1074             while (forwardCount++ < MAX_FORWARDS) {
1075                 // on every retry, reset this state information.
1076                 conn.setLastResponseInputStream(null);
1077 
1078                 if (LOG.isDebugEnabled()) {
1079                     LOG.debug("Execute loop try " + forwardCount);
1080                 }
1081 
1082                 // Discard status line
1083                 this.statusLine = null;
1084                 this.connectionCloseForced = false;
1085 
1086                 //write the request and read the response, will retry
1087                 processRequest(state, conn);
1088 
1089                 if (!isRetryNeeded(statusLine.getStatusCode(), state, conn)) {
1090                     // nope, no retry needed, exit loop.
1091                     break;
1092                 }
1093 
1094                 // retry - close previous stream.  Caution - this causes
1095                 // responseBodyConsumed to be called, which may also close the
1096                 // connection.
1097                 if (responseStream != null) {
1098                     responseStream.close();
1099                 }
1100 
1101             } //end of retry loop
1102 
1103             if (forwardCount >= MAX_FORWARDS) {
1104                 LOG.error("Narrowly avoided an infinite loop in execute");
1105                 throw new HttpRecoverableException("Maximum redirects ("
1106                     + MAX_FORWARDS + ") exceeded");
1107             }
1108 
1109         } finally {
1110             inExecute = false;
1111             // If the response has been fully processed, return the connection
1112             // to the pool.  Use this flag, rather than other tests (like
1113             // responseStream == null), as subclasses, might reset the stream,
1114             // for example, reading the entire response into a file and then
1115             // setting the file as the stream.
1116             if (doneWithConnection) {
1117                 ensureConnectionRelease();
1118             }
1119         }
1120 
1121         return statusLine.getStatusCode();
1122     }
1123 
1124     /***
1125      * Process the redirect response.
1126      * @param conn the {@link HttpConnection connection} used to execute
1127      *        this HTTP method
1128      * @return boolean <tt>true</tt> if the redirect was successful, <tt>false</tt>
1129      *        otherwise.
1130      */
1131     private boolean processRedirectResponse(HttpConnection conn) {
1132 
1133         if (!getFollowRedirects()) {
1134             LOG.info("Redirect requested but followRedirects is "
1135                     + "disabled");
1136             return false;
1137         }
1138 
1139         //get the location header to find out where to redirect to
1140         Header locationHeader = getResponseHeader("location");
1141         if (locationHeader == null) {
1142             // got a redirect response, but no location header
1143             LOG.error("Received redirect response " + getStatusCode()
1144                     + " but no location header");
1145             return false;
1146         }
1147         String location = locationHeader.getValue();
1148         if (LOG.isDebugEnabled()) {
1149             LOG.debug("Redirect requested to location '" + location
1150                     + "'");
1151         }
1152 
1153         //rfc2616 demands the location value be a complete URI
1154         //Location       = "Location" ":" absoluteURI
1155         URI redirectUri = null;
1156         URI currentUri = null;
1157 
1158         try {
1159             currentUri = new URI(
1160                 conn.getProtocol().getScheme(),
1161                 null,
1162                 conn.getHost(), 
1163                 conn.getPort(), 
1164                 this.getPath()
1165             );
1166             redirectUri = new URI(location.toCharArray());
1167             if (redirectUri.isRelativeURI()) {
1168                 if (isStrictMode()) {
1169                     LOG.warn("Redirected location '" + location 
1170                         + "' is not acceptable in strict mode");
1171                     return false;
1172                 } else { 
1173                     //location is incomplete, use current values for defaults
1174                     LOG.debug("Redirect URI is not absolute - parsing as relative");
1175                     redirectUri = new URI(currentUri, redirectUri);
1176                 }
1177             }
1178         } catch (URIException e) {
1179             LOG.warn("Redirected location '" + location + "' is malformed");
1180             return false;
1181         }
1182 
1183         //check for redirect to a different protocol, host or port
1184         try {
1185             checkValidRedirect(currentUri, redirectUri);
1186         } catch (HttpException ex) {
1187             //LOG the error and let the client handle the redirect
1188             LOG.warn(ex.getMessage());
1189             return false;
1190         }
1191 
1192         //invalidate the list of authentication attempts
1193         this.realms.clear();
1194         //remove exisitng authentication headers
1195         if (this.proxyAuthScheme instanceof NTLMScheme) {
1196             removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP);
1197         }
1198         removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP); 
1199         //update the current location with the redirect location.
1200         //avoiding use of URL.getPath() and URL.getQuery() to keep
1201         //jdk1.2 comliance.
1202         setPath(redirectUri.getEscapedPath());
1203         setQueryString(redirectUri.getEscapedQuery());
1204 
1205         if (LOG.isDebugEnabled()) {
1206             LOG.debug("Redirecting from '" + currentUri.getEscapedURI()
1207                 + "' to '" + redirectUri.getEscapedURI());
1208         }
1209 
1210         return true;
1211     }
1212 
1213     /***
1214      * Check for a valid redirect given the current connection and new URI.
1215      * Redirect to a different protocol, host or port are checked for validity.
1216      *
1217      * @param currentUri The current URI (redirecting from)
1218      * @param redirectUri The new URI to redirect to
1219      * @throws HttpException if the redirect is invalid
1220      * @since 2.0
1221      */
1222     private static void checkValidRedirect(URI currentUri, URI redirectUri)
1223     throws HttpException {
1224         LOG.trace("enter HttpMethodBase.checkValidRedirect(HttpConnection, URL)");
1225 
1226         String oldProtocol = currentUri.getScheme();
1227         String newProtocol = redirectUri.getScheme();
1228         if (!oldProtocol.equals(newProtocol)) {
1229             throw new HttpException("Redirect from protocol " + oldProtocol
1230                     + " to " + newProtocol + " is not supported");
1231         }
1232 
1233         try {
1234             String oldHost = currentUri.getHost();
1235             String newHost = redirectUri.getHost();
1236             if (!oldHost.equalsIgnoreCase(newHost)) {
1237                 throw new HttpException("Redirect from host " + oldHost
1238                         + " to " + newHost + " is not supported");
1239             }
1240         } catch (URIException e) {
1241             LOG.warn("Error getting URI host", e);
1242             throw new HttpException("Invalid Redirect URI from: " 
1243                 + currentUri.getEscapedURI() + " to: " + redirectUri.getEscapedURI()
1244             );
1245         }
1246 
1247         int oldPort = currentUri.getPort();
1248         if (oldPort < 0) {
1249             oldPort = getDefaultPort(oldProtocol);
1250         }
1251         int newPort = redirectUri.getPort();
1252         if (newPort < 0) {
1253             newPort = getDefaultPort(newProtocol);
1254         }
1255         if (oldPort != newPort) {
1256             throw new HttpException("Redirect from port " + oldPort
1257                     + " to " + newPort + " is not supported");
1258         }
1259     }
1260 
1261     /***
1262      * Returns the default port for the given protocol.
1263      *
1264      * @param protocol the given protocol.
1265      * @return the default port of the given protocol or -1 if the
1266      * protocol is not recognized.
1267      *
1268      * @since 2.0
1269      *
1270      */
1271     private static int getDefaultPort(String protocol) {
1272         String proto = protocol.toLowerCase().trim();
1273         if (proto.equals("http")) {
1274             return 80;
1275         } else if (proto.equals("https")) {
1276             return 443;
1277         }
1278         return -1;
1279     }
1280 
1281     /***
1282      * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed},
1283      * but not {@link #recycle recycled}.
1284      * 
1285      * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise
1286      */
1287     public boolean hasBeenUsed() {
1288         return used;
1289     }
1290 
1291     /***
1292      * Recycles the HTTP method so that it can be used again.
1293      * Note that all of the instance variables will be reset
1294      * once this method has been called. This method will also
1295      * release the connection being used by this HTTP method.
1296      * 
1297      * @see #releaseConnection()
1298      * 
1299      * @deprecated no longer supported and will be removed in the future
1300      *             version of HttpClient
1301      */
1302     public void recycle() {
1303         LOG.trace("enter HttpMethodBase.recycle()");
1304 
1305         releaseConnection();
1306 
1307         path = null;
1308         followRedirects = false;
1309         doAuthentication = true;
1310         authScheme = null;
1311         realm = null;
1312         proxyAuthScheme = null;
1313         proxyRealm = null;
1314         queryString = null;
1315         getRequestHeaderGroup().clear();
1316         getResponseHeaderGroup().clear();
1317         getResponseTrailerHeaderGroup().clear();
1318         statusLine = null;
1319         used = false;
1320         http11 = true;
1321         responseBody = null;
1322         recoverableExceptionCount = 0;
1323         inExecute = false;
1324         doneWithConnection = false;
1325         connectionCloseForced = false;
1326     }
1327 
1328     /***
1329      * Releases the connection being used by this HTTP method. In particular the
1330      * connection is used to read the response(if there is one) and will be held
1331      * until the response has been read. If the connection can be reused by other 
1332      * HTTP methods it is NOT closed at this point.
1333      *
1334      * @since 2.0
1335      */
1336     public void releaseConnection() {
1337 
1338         if (responseStream != null) {
1339             try {
1340                 // FYI - this may indirectly invoke responseBodyConsumed.
1341                 responseStream.close();
1342             } catch (IOException e) {
1343                 // the connection may not have been released, let's make sure
1344                 ensureConnectionRelease();
1345             }
1346         } else {
1347             // Make sure the connection has been released. If the response 
1348             // stream has not been set, this is the only way to release the 
1349             // connection. 
1350             ensureConnectionRelease();
1351         }
1352     }
1353 
1354     /***
1355      * Remove the request header associated with the given name. Note that
1356      * header-name matching is case insensitive.
1357      *
1358      * @param headerName the header name
1359      */
1360     public void removeRequestHeader(String headerName) {
1361         
1362         Header[] headers = getRequestHeaderGroup().getHeaders(headerName);
1363         for (int i = 0; i < headers.length; i++) {
1364             getRequestHeaderGroup().removeHeader(headers[i]);
1365         }
1366         
1367     }
1368 
1369     // ---------------------------------------------------------------- Queries
1370 
1371     /***
1372      * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise.
1373      * 
1374      * @return This implementation always returns <tt>true</tt>.
1375      */
1376     public boolean validate() {
1377         return true;
1378     }
1379 
1380     /***
1381      * Return the length (in bytes) of my request body, suitable for use in a
1382      * <tt>Content-Length</tt> header.
1383      *
1384      * <p>
1385      * Return <tt>-1</tt> when the content-length is unknown.
1386      * </p>
1387      *
1388      * <p>
1389      * This implementation returns <tt>0</tt>, indicating that the request has
1390      * no body.
1391      * </p>
1392      *
1393      * @return <tt>0</tt>, indicating that the request has no body.
1394      */
1395     protected int getRequestContentLength() {
1396         return 0;
1397     }
1398 
1399     /***
1400      * Generates <tt>Authorization</tt> request header if needed, as long as no
1401      * <tt>Authorization</tt> request header already exists.
1402      *
1403      * @param state the {@link HttpState state} information associated with this method
1404      * @param conn the {@link HttpConnection connection} used to execute
1405      *        this HTTP method
1406      *
1407      * @throws IOException if an I/O (transport) error occurs
1408      * @throws HttpException  if a protocol exception occurs.
1409      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1410      *                    Usually this kind of exceptions can be recovered from by
1411      *                    retrying the HTTP method 
1412      */
1413     protected void addAuthorizationRequestHeader(HttpState state,
1414                                                  HttpConnection conn)
1415     throws IOException, HttpException {
1416         LOG.trace("enter HttpMethodBase.addAuthorizationRequestHeader("
1417                   + "HttpState, HttpConnection)");
1418 
1419         // add authorization header, if needed
1420         if (getRequestHeader(HttpAuthenticator.WWW_AUTH_RESP) == null) {
1421             Header[] challenges = getResponseHeaderGroup().getHeaders(
1422                                                HttpAuthenticator.WWW_AUTH);
1423             if (challenges.length > 0) {
1424                 try {
1425                     this.authScheme = HttpAuthenticator.selectAuthScheme(challenges);
1426                     HttpAuthenticator.authenticate(this.authScheme, this, conn, state);
1427                 } catch (HttpException e) {
1428                     // log and move on
1429                     if (LOG.isErrorEnabled()) {
1430                         LOG.error(e.getMessage(), e);
1431                     }
1432                 }
1433             }
1434         }
1435     }
1436 
1437     /***
1438      * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
1439      * request header, as long as no <tt>Content-Length</tt> request header
1440      * already exists.
1441      * 
1442      * @param state the {@link HttpState state} information associated with this method
1443      * @param conn the {@link HttpConnection connection} used to execute
1444      *        this HTTP method
1445      *
1446      * @throws IOException if an I/O (transport) error occurs
1447      * @throws HttpException  if a protocol exception occurs.
1448      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1449      *                    Usually this kind of exceptions can be recovered from by
1450      *                    retrying the HTTP method 
1451      */
1452     protected void addContentLengthRequestHeader(HttpState state,
1453                                                  HttpConnection conn)
1454     throws IOException, HttpException {
1455         LOG.trace("enter HttpMethodBase.addContentLengthRequestHeader("
1456                   + "HttpState, HttpConnection)");
1457 
1458         // add content length or chunking
1459         int len = getRequestContentLength();
1460         if (getRequestHeader("content-length") == null) {
1461             if (0 < len) {
1462                 setRequestHeader("Content-Length", String.valueOf(len));
1463             } else if (http11 && (len < 0)) {
1464                 setRequestHeader("Transfer-Encoding", "chunked");
1465             }
1466         }
1467     }
1468 
1469     /***
1470      * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s
1471      * that match the given host, port and path.
1472      *
1473      * @param state the {@link HttpState state} information associated with this method
1474      * @param conn the {@link HttpConnection connection} used to execute
1475      *        this HTTP method
1476      *
1477      * @throws IOException if an I/O (transport) error occurs
1478      * @throws HttpException  if a protocol exception occurs.
1479      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1480      *                    Usually this kind of exceptions can be recovered from by
1481      *                    retrying the HTTP method 
1482      */
1483     protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
1484         throws IOException, HttpException {
1485 
1486         LOG.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
1487                   + "HttpConnection)");
1488 
1489         // Clean up the cookie headers
1490         removeRequestHeader("cookie");
1491 
1492         CookieSpec matcher = CookiePolicy.getSpecByPolicy(state.getCookiePolicy());
1493         Cookie[] cookies = matcher.match(conn.getHost(), conn.getPort(),
1494             getPath(), conn.isSecure(), state.getCookies());
1495         if ((cookies != null) && (cookies.length > 0)) {
1496             if (this.isStrictMode()) {
1497                 // In strict mode put all cookies on the same header
1498                 getRequestHeaderGroup().addHeader(
1499                   matcher.formatCookieHeader(cookies));
1500             } else {
1501                 // In non-strict mode put each cookie on a separate header
1502                 for (int i = 0; i < cookies.length; i++) {
1503                     getRequestHeaderGroup().addHeader(
1504                       matcher.formatCookieHeader(cookies[i]));
1505                 }
1506             }
1507         }
1508     }
1509 
1510     /***
1511      * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
1512      * header already exists.
1513      *
1514      * @param state the {@link HttpState state} information associated with this method
1515      * @param conn the {@link HttpConnection connection} used to execute
1516      *        this HTTP method
1517      *
1518      * @throws IOException if an I/O (transport) error occurs
1519      * @throws HttpException  if a protocol exception occurs.
1520      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1521      *                    Usually this kind of exceptions can be recovered from by
1522      *                    retrying the HTTP method 
1523      */
1524     protected void addHostRequestHeader(HttpState state, HttpConnection conn)
1525     throws IOException, HttpException {
1526         LOG.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
1527                   + "HttpConnection)");
1528 
1529         // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
1530         // applications to send the Host request-header.
1531         // TODO: Add the ability to disable the sending of this header for
1532         //       HTTP/1.0 requests.
1533         String host = conn.getVirtualHost();
1534         if (host != null) {
1535             LOG.debug("Using virtual host name: " + host);
1536         } else {
1537             host = conn.getHost();
1538         }
1539         int port = conn.getPort();
1540 
1541         if (getRequestHeader("host") != null) {
1542             LOG.debug(
1543                 "Request to add Host header ignored: header already added");
1544             return;
1545         }
1546 
1547         // Note: RFC 2616 uses the term "internet host name" for what goes on the
1548         // host line.  It would seem to imply that host should be blank if the
1549         // host is a number instead of an name.  Based on the behavior of web
1550         // browsers, and the fact that RFC 2616 never defines the phrase "internet
1551         // host name", and the bad behavior of HttpClient that follows if we
1552         // send blank, I interpret this as a small misstatement in the RFC, where
1553         // they meant to say "internet host".  So IP numbers get sent as host
1554         // entries too. -- Eric Johnson 12/13/2002
1555         if (LOG.isDebugEnabled()) {
1556             LOG.debug("Adding Host request header");
1557         }
1558 
1559         //appends the port only if not using the default port for the protocol
1560         if (conn.getProtocol().getDefaultPort() != port) {
1561             host += (":" + port);
1562         }
1563 
1564         setRequestHeader("Host", host);
1565     }
1566 
1567     /***
1568      * Generates <tt>Proxy-Authorization</tt> request header if needed, as long as no
1569      * <tt>Proxy-Authorization</tt> request header already exists.
1570      *
1571      * @param state the {@link HttpState state} information associated with this method
1572      * @param conn the {@link HttpConnection connection} used to execute
1573      *        this HTTP method
1574      *
1575      * @throws IOException if an I/O (transport) error occurs
1576      * @throws HttpException  if a protocol exception occurs.
1577      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1578      *                    Usually this kind of exceptions can be recovered from by
1579      *                    retrying the HTTP method 
1580      */
1581     protected void addProxyAuthorizationRequestHeader(HttpState state,
1582                                                       HttpConnection conn)
1583     throws IOException, HttpException {
1584         LOG.trace("enter HttpMethodBase.addProxyAuthorizationRequestHeader("
1585                   + "HttpState, HttpConnection)");
1586 
1587         // add proxy authorization header, if needed
1588         if (getRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP) == null) {
1589             Header[] challenges = getResponseHeaderGroup().getHeaders(
1590                                                HttpAuthenticator.PROXY_AUTH);
1591             if (challenges.length > 0) {
1592                 try {
1593                     this.proxyAuthScheme = HttpAuthenticator.selectAuthScheme(challenges);
1594                     HttpAuthenticator.authenticateProxy(this.proxyAuthScheme, this, conn, state);
1595                 } catch (HttpException e) {
1596                     // log and move on
1597                     if (LOG.isErrorEnabled()) {
1598                         LOG.error(e.getMessage(), e);
1599                     }
1600                 }
1601             }
1602         }
1603     }
1604 
1605     /***
1606      * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when 
1607      * communicating via a proxy server.
1608      *
1609      * @param state the {@link HttpState state} information associated with this method
1610      * @param conn the {@link HttpConnection connection} used to execute
1611      *        this HTTP method
1612      *
1613      * @throws IOException if an I/O (transport) error occurs
1614      * @throws HttpException  if a protocol exception occurs.
1615      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1616      *                    Usually this kind of exceptions can be recovered from by
1617      *                    retrying the HTTP method 
1618      */
1619     protected void addProxyConnectionHeader(HttpState state,
1620                                             HttpConnection conn)
1621     throws IOException, HttpException {
1622         LOG.trace("enter HttpMethodBase.addProxyConnectionHeader("
1623                   + "HttpState, HttpConnection)");
1624         if (!conn.isTransparent()) {
1625             setRequestHeader("Proxy-Connection", "Keep-Alive");
1626         }
1627     }
1628 
1629     /***
1630      * Generates all the required request {@link Header header}s 
1631      * to be submitted via the given {@link HttpConnection connection}.
1632      *
1633      * <p>
1634      * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
1635      * <tt>Cookie</tt>, <tt>Content-Length</tt>, <tt>Transfer-Encoding</tt>,
1636      * and <tt>Authorization</tt> headers, when appropriate.
1637      * </p>
1638      *
1639      * <p>
1640      * Subclasses may want to override this method to to add additional
1641      * headers, and may choose to invoke this implementation (via
1642      * <tt>super</tt>) to add the "standard" headers.
1643      * </p>
1644      *
1645      * @param state the {@link HttpState state} information associated with this method
1646      * @param conn the {@link HttpConnection connection} used to execute
1647      *        this HTTP method
1648      *
1649      * @throws IOException if an I/O (transport) error occurs
1650      * @throws HttpException  if a protocol exception occurs.
1651      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1652      *                    Usually this kind of exceptions can be recovered from by
1653      *                    retrying the HTTP method 
1654      *
1655      * @see #writeRequestHeaders
1656      */
1657     protected void addRequestHeaders(HttpState state, HttpConnection conn)
1658     throws IOException, HttpException {
1659         LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
1660             + "HttpConnection)");
1661 
1662         addUserAgentRequestHeader(state, conn);
1663         addHostRequestHeader(state, conn);
1664         addCookieRequestHeader(state, conn);
1665         addAuthorizationRequestHeader(state, conn);
1666         addProxyAuthorizationRequestHeader(state, conn);
1667         addProxyConnectionHeader(state, conn);
1668         addContentLengthRequestHeader(state, conn);
1669     }
1670 
1671     /***
1672      * Generates default <tt>User-Agent</tt> request header, as long as no
1673      * <tt>User-Agent</tt> request header already exists.
1674      *
1675      * @param state the {@link HttpState state} information associated with this method
1676      * @param conn the {@link HttpConnection connection} used to execute
1677      *        this HTTP method
1678      *
1679      * @throws IOException if an I/O (transport) error occurs
1680      * @throws HttpException  if a protocol exception occurs.
1681      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1682      *                    Usually this kind of exceptions can be recovered from by
1683      *                    retrying the HTTP method 
1684      */
1685     protected void addUserAgentRequestHeader(HttpState state,
1686                                              HttpConnection conn)
1687     throws IOException, HttpException {
1688         LOG.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
1689             + "HttpConnection)");
1690 
1691         if (getRequestHeader("user-agent") == null) {
1692             setRequestHeader(HttpMethodBase.USER_AGENT);
1693         }
1694     }
1695 
1696     /***
1697      * Throws an {@link IllegalStateException} if the HTTP method has been already
1698      * {@link #execute executed}, but not {@link #recycle recycled}.
1699      *
1700      * @throws IllegalStateException if the method has been used and not
1701      *      recycled
1702      */
1703     protected void checkNotUsed() throws IllegalStateException {
1704         if (used) {
1705             throw new IllegalStateException("Already used.");
1706         }
1707     }
1708 
1709     /***
1710      * Throws an {@link IllegalStateException} if the HTTP method has not been
1711      * {@link #execute executed} since last {@link #recycle recycle}.
1712      *
1713      *
1714      * @throws IllegalStateException if not used
1715      */
1716     protected void checkUsed()  throws IllegalStateException {
1717         if (!used) {
1718             throw new IllegalStateException("Not Used.");
1719         }
1720     }
1721 
1722     // ------------------------------------------------- Static Utility Methods
1723 
1724     /***
1725      * Generates HTTP request line according to the specified attributes.
1726      *
1727      * @param connection the {@link HttpConnection connection} used to execute
1728      *        this HTTP method
1729      * @param name the method name generate a request for
1730      * @param requestPath the path string for the request
1731      * @param query the query string for the request
1732      * @param version the protocol version to use (e.g. HTTP/1.0)
1733      *
1734      * @return HTTP request line
1735      */
1736     protected static String generateRequestLine(HttpConnection connection,
1737         String name, String requestPath, String query, String version) {
1738         LOG.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
1739             + "String, String, String, String)");
1740 
1741         StringBuffer buf = new StringBuffer();
1742         // Append method name
1743         buf.append(name);
1744         buf.append(" ");
1745         // Absolute or relative URL?
1746         if (!connection.isTransparent()) {
1747             Protocol protocol = connection.getProtocol();
1748             buf.append(protocol.getScheme().toLowerCase());
1749             buf.append("://");
1750             buf.append(connection.getHost());
1751             if ((connection.getPort() != -1) 
1752                 && (connection.getPort() != protocol.getDefaultPort())
1753             ) {
1754                 buf.append(":");
1755                 buf.append(connection.getPort());
1756             }
1757         }
1758         // Append path, if any
1759         if (requestPath == null) {
1760             buf.append("/");
1761         } else {
1762             if (!connection.isTransparent() && !requestPath.startsWith("/")) {
1763                 buf.append("/");
1764             }
1765             buf.append(requestPath);
1766         }
1767         // Append query, if any
1768         if (query != null) {
1769             if (query.indexOf("?") != 0) {
1770                 buf.append("?");
1771             }
1772             buf.append(query);
1773         }
1774         // Append protocol
1775         buf.append(" ");
1776         buf.append(version);
1777         buf.append("\r\n");
1778         
1779         return buf.toString();
1780     }
1781     
1782     /***
1783      * This method is invoked immediately after 
1784      * {@link #readResponseBody(HttpState,HttpConnection)} and can be overridden by
1785      * sub-classes in order to provide custom body processing.
1786      *
1787      * <p>
1788      * This implementation does nothing.
1789      * </p>
1790      *
1791      * @param state the {@link HttpState state} information associated with this method
1792      * @param conn the {@link HttpConnection connection} used to execute
1793      *        this HTTP method
1794      *
1795      * @see #readResponse
1796      * @see #readResponseBody
1797      */
1798     protected void processResponseBody(HttpState state, HttpConnection conn) {
1799     }
1800 
1801     /***
1802      * This method is invoked immediately after 
1803      * {@link #readResponseHeaders(HttpState,HttpConnection)} and can be overridden by
1804      * sub-classes in order to provide custom response headers processing.
1805 
1806      * <p>
1807      * This implementation will handle the <tt>Set-Cookie</tt> and
1808      * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
1809      * the given {@link HttpState}.
1810      * </p>
1811      *
1812      * @param state the {@link HttpState state} information associated with this method
1813      * @param conn the {@link HttpConnection connection} used to execute
1814      *        this HTTP method
1815      *
1816      * @see #readResponse
1817      * @see #readResponseHeaders
1818      */
1819     protected void processResponseHeaders(HttpState state,
1820         HttpConnection conn) {
1821         LOG.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
1822             + "HttpConnection)");
1823 
1824         Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie2");
1825         //Only process old style set-cookie headers if new style headres
1826         //are not present
1827         if (headers.length == 0) { 
1828             headers = getResponseHeaderGroup().getHeaders("set-cookie");
1829         }
1830         
1831         CookieSpec parser = CookiePolicy.getSpecByPolicy(state.getCookiePolicy());
1832         for (int i = 0; i < headers.length; i++) {
1833             Header header = headers[i];
1834             Cookie[] cookies = null;
1835             try {
1836                 cookies = parser.parse(
1837                   conn.getHost(),
1838                   conn.getPort(),
1839                   getPath(),
1840                   conn.isSecure(),
1841                   header);
1842             } catch (MalformedCookieException e) {
1843                 if (LOG.isWarnEnabled()) {
1844                     LOG.warn("Invalid cookie header: \"" 
1845                         + header.getValue() 
1846                         + "\". " + e.getMessage());
1847                 }
1848             }
1849             if (cookies != null) {
1850                 for (int j = 0; j < cookies.length; j++) {
1851                     Cookie cookie = cookies[j];
1852                     try {
1853                         parser.validate(
1854                           conn.getHost(),
1855                           conn.getPort(),
1856                           getPath(),
1857                           conn.isSecure(),
1858                           cookie);
1859                         state.addCookie(cookie);
1860                         if (LOG.isDebugEnabled()) {
1861                             LOG.debug("Cookie accepted: \"" 
1862                                 + parser.formatCookie(cookie) + "\"");
1863                         }
1864                     } catch (MalformedCookieException e) {
1865                         if (LOG.isWarnEnabled()) {
1866                             LOG.warn("Cookie rejected: \"" + parser.formatCookie(cookie) 
1867                                 + "\". " + e.getMessage());
1868                         }
1869                     }
1870                 }
1871             }
1872         }
1873     }
1874 
1875     /***
1876      * This method is invoked immediately after 
1877      * {@link #readStatusLine(HttpState,HttpConnection)} and can be overridden by
1878      * sub-classes in order to provide custom response status line processing.
1879      *
1880      * @param state the {@link HttpState state} information associated with this method
1881      * @param conn the {@link HttpConnection connection} used to execute
1882      *        this HTTP method
1883      *
1884      * @see #readResponse
1885      * @see #readStatusLine
1886      */
1887     protected void processStatusLine(HttpState state, HttpConnection conn) {
1888     }
1889 
1890     /***
1891      * Reads the response from the given {@link HttpConnection connection}.
1892      *
1893      * <p>
1894      * The response is processed as the following sequence of actions:
1895      *
1896      * <ol>
1897      * <li>
1898      * {@link #readStatusLine(HttpState,HttpConnection)} is
1899      * invoked to read the request line.
1900      * </li>
1901      * <li>
1902      * {@link #processStatusLine(HttpState,HttpConnection)}
1903      * is invoked, allowing the method to process the status line if
1904      * desired.
1905      * </li>
1906      * <li>
1907      * {@link #readResponseHeaders(HttpState,HttpConnection)} is invoked to read
1908      * the associated headers.
1909      * </li>
1910      * <li>
1911      * {@link #processResponseHeaders(HttpState,HttpConnection)} is invoked, allowing
1912      * the method to process the headers if desired.
1913      * </li>
1914      * <li>
1915      * {@link #readResponseBody(HttpState,HttpConnection)} is
1916      * invoked to read the associated body (if any).
1917      * </li>
1918      * <li>
1919      * {@link #processResponseBody(HttpState,HttpConnection)} is invoked, allowing the
1920      * method to process the response body if desired.
1921      * </li>
1922      * </ol>
1923      *
1924      * Subclasses may want to override one or more of the above methods to to
1925      * customize the processing. (Or they may choose to override this method
1926      * if dramatically different processing is required.)
1927      * </p>
1928      *
1929      * @param state the {@link HttpState state} information associated with this method
1930      * @param conn the {@link HttpConnection connection} used to execute
1931      *        this HTTP method
1932      *
1933      * @throws HttpException  if a protocol exception occurs.
1934      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1935      *                    Usually this kind of exceptions can be recovered from by
1936      *                    retrying the HTTP method 
1937      */
1938     protected void readResponse(HttpState state, HttpConnection conn)
1939     throws HttpException {
1940         LOG.trace(
1941             "enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
1942         try {
1943             // Status line & line may have already been received
1944             // if 'expect - continue' handshake has been used
1945             while (this.statusLine == null) {
1946                 readStatusLine(state, conn);
1947                 processStatusLine(state, conn);
1948                 readResponseHeaders(state, conn);
1949                 processResponseHeaders(state, conn);
1950                 
1951                 int status = this.statusLine.getStatusCode();
1952                 if ((status >= 100) && (status < 200)) {
1953                     if (LOG.isInfoEnabled()) {
1954                         LOG.info("Discarding unexpected response: " + this.statusLine.toString()); 
1955                     }
1956                     this.statusLine = null;
1957                 }
1958             }
1959             readResponseBody(state, conn);
1960             processResponseBody(state, conn);
1961         } catch (IOException e) {
1962             throw new HttpRecoverableException(e.toString());
1963         }
1964     }
1965 
1966     /***
1967      * Read the response body from the given {@link HttpConnection}.
1968      *
1969      * <p>
1970      * The current implementation wraps the socket level stream with
1971      * an appropriate stream for the type of response (chunked, content-length,
1972      * or auto-close).  If there is no response body, the connection associated
1973      * with the request will be returned to the connection manager.
1974      * </p>
1975      *
1976      * <p>
1977      * Subclasses may want to override this method to to customize the
1978      * processing.
1979      * </p>
1980      *
1981      * @param state the {@link HttpState state} information associated with this method
1982      * @param conn the {@link HttpConnection connection} used to execute
1983      *        this HTTP method
1984      *
1985      * @throws IOException if an I/O (transport) error occurs
1986      * @throws HttpException  if a protocol exception occurs.
1987      * @throws HttpRecoverableException if a recoverable transport error occurs. 
1988      *                    Usually this kind of exceptions can be recovered from by
1989      *                    retrying the HTTP method 
1990      *
1991      * @see #readResponse
1992      * @see #processResponseBody
1993      */
1994     protected void readResponseBody(HttpState state, HttpConnection conn)
1995     throws IOException, HttpException {
1996         LOG.trace(
1997             "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1998 
1999         // assume we are not done with the connection if we get a stream
2000         doneWithConnection = false;
2001         InputStream stream = readResponseBody(conn);
2002         if (stream == null) {
2003             // done using the connection!
2004             responseBodyConsumed();
2005         } else {
2006             conn.setLastResponseInputStream(stream);
2007             setResponseStream(stream);
2008         }
2009     }
2010 
2011     /***
2012      * Returns the response body as an {@link InputStream input stream}
2013      * corresponding to the values of the <tt>Content-Length</tt> and 
2014      * <tt>Transfer-Encoding</tt> headers. If no response body is available
2015      * returns <tt>null</tt>.
2016      * <p>
2017      *
2018      * @see #readResponse
2019      * @see #processResponseBody
2020      *
2021      * @param conn the {@link HttpConnection connection} used to execute
2022      *        this HTTP method
2023      *
2024      * @throws IOException if an I/O (transport) error occurs
2025      * @throws HttpException  if a protocol exception occurs.
2026      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2027      *                    Usually this kind of exceptions can be recovered from by
2028      *                    retrying the HTTP method 
2029      */
2030     private InputStream readResponseBody(HttpConnection conn)
2031         throws IOException {
2032 
2033         LOG.trace("enter HttpMethodBase.readResponseBody(HttpConnection)");
2034 
2035         responseBody = null;
2036         InputStream is = conn.getResponseInputStream();
2037         if (Wire.CONTENT_WIRE.enabled()) {
2038             is = new WireLogInputStream(is, Wire.CONTENT_WIRE);
2039         }
2040         InputStream result = null;
2041         Header transferEncodingHeader = responseHeaders.getFirstHeader("Transfer-Encoding");
2042         // We use Transfer-Encoding if present and ignore Content-Length.
2043         // RFC2616, 4.4 item number 3
2044         if (transferEncodingHeader != null) {
2045 
2046             String transferEncoding = transferEncodingHeader.getValue();
2047             if (!"chunked".equalsIgnoreCase(transferEncoding) 
2048                 && !"identity".equalsIgnoreCase(transferEncoding)) {
2049                 if (LOG.isWarnEnabled()) {
2050                     LOG.warn("Unsupported transfer encoding: " + transferEncoding);
2051                 }
2052             }
2053             HeaderElement[] encodings = transferEncodingHeader.getValues();
2054             // The chunck encoding must be the last one applied
2055             // RFC2616, 14.41
2056             int len = encodings.length;            
2057             if ((len > 0) && ("chunked".equalsIgnoreCase(encodings[len - 1].getName()))) { 
2058                 // if response body is empty
2059                 if (conn.isResponseAvailable(conn.getSoTimeout())) {
2060                     result = new ChunkedInputStream(is, this);
2061                 } else {
2062                     if (isStrictMode()) {
2063                         throw new HttpException("Chunk-encoded body declared but not sent");
2064                     } else {
2065                         LOG.warn("Chunk-encoded body missing");
2066                     }
2067                 }
2068             } else {
2069                 LOG.info("Response content is not chunk-encoded");
2070                 // The connection must be terminated by closing 
2071                 // the socket as per RFC 2616, 3.6
2072                 setConnectionCloseForced(true);
2073                 result = is;  
2074             }
2075         } else {
2076             int expectedLength = getResponseContentLength();
2077             if (expectedLength == -1) {
2078                 if (canResponseHaveBody(statusLine.getStatusCode())) {
2079                     Header connectionHeader = responseHeaders.getFirstHeader("Connection");
2080                     String connectionDirective = null;
2081                     if (connectionHeader != null) {
2082                         connectionDirective = connectionHeader.getValue();
2083                     }
2084                     if (isHttp11() && !"close".equalsIgnoreCase(connectionDirective)) {
2085                         LOG.info("Response content length is not known");
2086                         setConnectionCloseForced(true);
2087                     }
2088                     result = is;            
2089                 }
2090             } else {
2091                 result = new ContentLengthInputStream(is, expectedLength);
2092             }
2093         } 
2094         // if there is a result - ALWAYS wrap it in an observer which will
2095         // close the underlying stream as soon as it is consumed, and notify
2096         // the watcher that the stream has been consumed.
2097         if (result != null) {
2098 
2099             result = new AutoCloseInputStream(
2100                 result,
2101                 new ResponseConsumedWatcher() {
2102                     public void responseConsumed() {
2103                         responseBodyConsumed();
2104                     }
2105                 }
2106             );
2107         }
2108 
2109         return result;
2110     }
2111 
2112     /***
2113      * Reads the response headers from the given {@link HttpConnection connection}.
2114      *
2115      * <p>
2116      * Subclasses may want to override this method to to customize the
2117      * processing.
2118      * </p>
2119      *
2120      * <p>
2121      * "It must be possible to combine the multiple header fields into one
2122      * "field-name: field-value" pair, without changing the semantics of the
2123      * message, by appending each subsequent field-value to the first, each
2124      * separated by a comma." - HTTP/1.0 (4.3)
2125      * </p>
2126      *
2127      * @param state the {@link HttpState state} information associated with this method
2128      * @param conn the {@link HttpConnection connection} used to execute
2129      *        this HTTP method
2130      *
2131      * @throws IOException if an I/O (transport) error occurs
2132      * @throws HttpException  if a protocol exception occurs.
2133      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2134      *                    Usually this kind of exceptions can be recovered from by
2135      *                    retrying the HTTP method 
2136      *
2137      * @see #readResponse
2138      * @see #processResponseHeaders
2139      */
2140     protected void readResponseHeaders(HttpState state, HttpConnection conn)
2141     throws IOException, HttpException {
2142         LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
2143             + "HttpConnection)");
2144 
2145         getResponseHeaderGroup().clear();
2146         Header[] headers = HttpParser.parseHeaders(conn.getResponseInputStream());
2147         if (Wire.HEADER_WIRE.enabled()) {
2148             for (int i = 0; i < headers.length; i++) {
2149                 Wire.HEADER_WIRE.input(headers[i].toExternalForm());
2150             }
2151         }
2152         getResponseHeaderGroup().setHeaders(headers);
2153     }
2154 
2155     /***
2156      * Read the status line from the given {@link HttpConnection}, setting my
2157      * {@link #getStatusCode status code} and {@link #getStatusText status
2158      * text}.
2159      *
2160      * <p>
2161      * Subclasses may want to override this method to to customize the
2162      * processing.
2163      * </p>
2164      *
2165      * @param state the {@link HttpState state} information associated with this method
2166      * @param conn the {@link HttpConnection connection} used to execute
2167      *        this HTTP method
2168      *
2169      * @throws IOException if an I/O (transport) error occurs
2170      * @throws HttpException  if a protocol exception occurs.
2171      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2172      *                    Usually this kind of exceptions can be recovered from by
2173      *                    retrying the HTTP method 
2174      *
2175      * @see StatusLine
2176      */
2177     protected void readStatusLine(HttpState state, HttpConnection conn)
2178     throws IOException, HttpRecoverableException, HttpException {
2179         LOG.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
2180 
2181         //read out the HTTP status string
2182         String s = conn.readLine();
2183         while ((s != null) && !StatusLine.startsWithHTTP(s)) {
2184             if (Wire.HEADER_WIRE.enabled()) {
2185                 Wire.HEADER_WIRE.input(s + "\r\n");
2186             }
2187             s = conn.readLine();
2188         }
2189         if (s == null) {
2190             // A null statusString means the connection was lost before we got a
2191             // response.  Try again.
2192             throw new HttpRecoverableException("Error in parsing the status "
2193                 + " line from the response: unable to find line starting with"
2194                 + " \"HTTP\"");
2195         }
2196         if (Wire.HEADER_WIRE.enabled()) {
2197             Wire.HEADER_WIRE.input(s + "\r\n");
2198         }
2199         //create the status line from the status string
2200         statusLine = new StatusLine(s);
2201 
2202         //check for a valid HTTP-Version
2203         String httpVersion = statusLine.getHttpVersion();
2204         if (httpVersion.equals("HTTP/1.0")) {
2205             http11 = false;
2206         } else if (httpVersion.equals("HTTP/1.1")) {
2207             http11 = true;
2208         } else if (httpVersion.equals("HTTP")) {
2209             // some servers do not specify the version correctly, we will just assume 1.0
2210             http11 = false;
2211         } else {
2212             throw new HttpException("Unrecognized server protocol: '"
2213                                     + httpVersion + "'");
2214         }
2215 
2216     }
2217 
2218     // ------------------------------------------------------ Protected Methods
2219 
2220     /***
2221      * <p>
2222      * Sends the request via the given {@link HttpConnection connection}.
2223      * </p>
2224      *
2225      * <p>
2226      * The request is written as the following sequence of actions:
2227      * </p>
2228      *
2229      * <ol>
2230      * <li>
2231      * {@link #writeRequestLine(HttpState, HttpConnection)} is invoked to 
2232      * write the request line.
2233      * </li>
2234      * <li>
2235      * {@link #writeRequestHeaders(HttpState, HttpConnection)} is invoked 
2236      * to write the associated headers.
2237      * </li>
2238      * <li>
2239      * <tt>\r\n</tt> is sent to close the head part of the request.
2240      * </li>
2241      * <li>
2242      * {@link #writeRequestBody(HttpState, HttpConnection)} is invoked to 
2243      * write the body part of the request.
2244      * </li>
2245      * </ol>
2246      *
2247      * <p>
2248      * Subclasses may want to override one or more of the above methods to to
2249      * customize the processing. (Or they may choose to override this method
2250      * if dramatically different processing is required.)
2251      * </p>
2252      *
2253      * @param state the {@link HttpState state} information associated with this method
2254      * @param conn the {@link HttpConnection connection} used to execute
2255      *        this HTTP method
2256      *
2257      * @throws IOException if an I/O (transport) error occurs
2258      * @throws HttpException  if a protocol exception occurs.
2259      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2260      *                    Usually this kind of exceptions can be recovered from by
2261      *                    retrying the HTTP method 
2262      */
2263     protected void writeRequest(HttpState state, HttpConnection conn)
2264     throws IOException, HttpException {
2265         LOG.trace(
2266             "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
2267         writeRequestLine(state, conn);
2268         writeRequestHeaders(state, conn);
2269         conn.writeLine(); // close head
2270         // make sure the status line and headers have been sent
2271         conn.flushRequestOutputStream();
2272         if (Wire.HEADER_WIRE.enabled()) {
2273             Wire.HEADER_WIRE.output("\r\n");
2274         }
2275 
2276         Header expectheader = getRequestHeader("Expect");
2277         String expectvalue = null;
2278         if (expectheader != null) {
2279             expectvalue = expectheader.getValue();
2280         }
2281         if ((expectvalue != null) 
2282          && (expectvalue.compareToIgnoreCase("100-continue") == 0)) {
2283             if (this.isHttp11()) {
2284                 int readTimeout = conn.getSoTimeout();
2285                 try {
2286                     conn.setSoTimeout(RESPONSE_WAIT_TIME_MS);
2287                     readStatusLine(state, conn);
2288                     processStatusLine(state, conn);
2289                     readResponseHeaders(state, conn);
2290                     processResponseHeaders(state, conn);
2291 
2292                     if (this.statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
2293                         // Discard status line
2294                         this.statusLine = null;
2295                         LOG.debug("OK to continue received");
2296                     } else {
2297                         return;
2298                     }
2299                 } catch (InterruptedIOException e) {
2300                     // Most probably Expect header is not recongnized
2301                     // Remove the header to signal the method 
2302                     // that it's okay to go ahead with sending data
2303                     removeRequestHeader("Expect");
2304                     LOG.info("100 (continue) read timeout. Resume sending the request");
2305                 } finally {
2306                     conn.setSoTimeout(readTimeout);
2307                 }
2308                 
2309             } else {
2310                 removeRequestHeader("Expect");
2311                 LOG.info("'Expect: 100-continue' handshake is only supported by "
2312                     + "HTTP/1.1 or higher");
2313             }
2314         }
2315 
2316         writeRequestBody(state, conn);
2317         // make sure the entire request body has been sent
2318         conn.flushRequestOutputStream();
2319     }
2320 
2321     /***
2322      * Writes the request body to the given {@link HttpConnection connection}.
2323      *
2324      * <p>
2325      * This method should return <tt>true</tt> if the request body was actually
2326      * sent (or is empty), or <tt>false</tt> if it could not be sent for some
2327      * reason.
2328      * </p>
2329      *
2330      * <p>
2331      * This implementation writes nothing and returns <tt>true</tt>.
2332      * </p>
2333      *
2334      * @param state the {@link HttpState state} information associated with this method
2335      * @param conn the {@link HttpConnection connection} used to execute
2336      *        this HTTP method
2337      *
2338      * @return <tt>true</tt>
2339      *
2340      * @throws IOException if an I/O (transport) error occurs
2341      * @throws HttpException  if a protocol exception occurs.
2342      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2343      *                    Usually this kind of exceptions can be recovered from by
2344      *                    retrying the HTTP method 
2345      */
2346     protected boolean writeRequestBody(HttpState state, HttpConnection conn)
2347     throws IOException, HttpException {
2348         return true;
2349     }
2350 
2351     /***
2352      * Writes the request headers to the given {@link HttpConnection connection}.
2353      *
2354      * <p>
2355      * This implementation invokes {@link #addRequestHeaders(HttpState,HttpConnection)},
2356      * and then writes each header to the request stream.
2357      * </p>
2358      *
2359      * <p>
2360      * Subclasses may want to override this method to to customize the
2361      * processing.
2362      * </p>
2363      *
2364      * @param state the {@link HttpState state} information associated with this method
2365      * @param conn the {@link HttpConnection connection} used to execute
2366      *        this HTTP method
2367      *
2368      * @throws IOException if an I/O (transport) error occurs
2369      * @throws HttpException  if a protocol exception occurs.
2370      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2371      *                    Usually this kind of exceptions can be recovered from by
2372      *                    retrying the HTTP method 
2373      *
2374      * @see #addRequestHeaders
2375      * @see #getRequestHeaders
2376      */
2377     protected void writeRequestHeaders(HttpState state, HttpConnection conn)
2378     throws IOException, HttpException {
2379         LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
2380             + "HttpConnection)");
2381         addRequestHeaders(state, conn);
2382 
2383         Header[] headers = getRequestHeaders();
2384         for (int i = 0; i < headers.length; i++) {
2385             String s = headers[i].toExternalForm();
2386             if (Wire.HEADER_WIRE.enabled()) {
2387                 Wire.HEADER_WIRE.output(s);
2388             }
2389             conn.print(s);
2390         }
2391     }
2392 
2393     /***
2394      * Writes the request line to the given {@link HttpConnection connection}.
2395      *
2396      * <p>
2397      * Subclasses may want to override this method to to customize the
2398      * processing.
2399      * </p>
2400      *
2401      * @param state the {@link HttpState state} information associated with this method
2402      * @param conn the {@link HttpConnection connection} used to execute
2403      *        this HTTP method
2404      *
2405      * @throws IOException if an I/O (transport) error occurs
2406      * @throws HttpException  if a protocol exception occurs.
2407      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2408      *                    Usually this kind of exceptions can be recovered from by
2409      *                    retrying the HTTP method 
2410      *
2411      * @see #generateRequestLine
2412      */
2413     protected void writeRequestLine(HttpState state, HttpConnection conn)
2414     throws IOException, HttpException {
2415         LOG.trace(
2416             "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
2417         String requestLine = getRequestLine(conn);
2418         if (Wire.HEADER_WIRE.enabled()) {
2419             Wire.HEADER_WIRE.output(requestLine);
2420         }
2421         conn.print(requestLine);
2422     }
2423 
2424     /***
2425      * Returns the request line.
2426      * 
2427      * @param conn the {@link HttpConnection connection} used to execute
2428      *        this HTTP method
2429      * 
2430      * @return The request line.
2431      */
2432     private String getRequestLine(HttpConnection conn) {
2433         return  HttpMethodBase.generateRequestLine(conn, getName(),
2434                 getPath(), getQueryString(), getHttpVersion());
2435     }
2436 
2437     /***
2438      * Get the HTTP version.
2439      *
2440      * @return HTTP/1.1 if version 1.1 of HTTP protocol is used, HTTP/1.0 otherwise
2441      *
2442      * @since 2.0
2443      */
2444     private String getHttpVersion() {
2445         return (http11 ? "HTTP/1.1" : "HTTP/1.0");
2446     }
2447 
2448     /***
2449      * Per RFC 2616 section 4.3, some response can never contain a message
2450      * body.
2451      *
2452      * @param status - the HTTP status code
2453      *
2454      * @return <tt>true</tt> if the message may contain a body, <tt>false</tt> if it can not
2455      *         contain a message body
2456      */
2457     private static boolean canResponseHaveBody(int status) {
2458         LOG.trace("enter HttpMethodBase.canResponseHaveBody(int)");
2459 
2460         boolean result = true;
2461 
2462         if ((status >= 100 && status <= 199) || (status == 204)
2463             || (status == 304)) { // NOT MODIFIED
2464             result = false;
2465         }
2466 
2467         return result;
2468     }
2469 
2470     /***
2471      * Processes a response that requires authentication
2472      *
2473      * @param state the {@link HttpState state} information associated with this method
2474      * @param conn the {@link HttpConnection connection} used to execute
2475      *        this HTTP method
2476      *
2477      * @return true if the request has completed process, false if more
2478      *         attempts are needed
2479      */
2480     private boolean processAuthenticationResponse(HttpState state, HttpConnection conn) {
2481         LOG.trace("enter HttpMethodBase.processAuthenticationResponse("
2482             + "HttpState, HttpConnection)");
2483 
2484         if (this.proxyAuthScheme instanceof NTLMScheme) {
2485             removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP);
2486         }
2487         if (this.authScheme instanceof NTLMScheme) {
2488             removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP);
2489         }
2490         int statusCode = statusLine.getStatusCode();
2491         // handle authentication required
2492         Header[] challenges = null;
2493         Set realmsUsed = null;
2494         String host = null;
2495         switch (statusCode) {
2496             case HttpStatus.SC_UNAUTHORIZED:
2497                 challenges = getResponseHeaderGroup().getHeaders(HttpAuthenticator.WWW_AUTH);
2498                 realmsUsed = realms;
2499                 host = conn.getVirtualHost();
2500                 if (host == null) {
2501                     host = conn.getHost();
2502                 }
2503                 break;
2504             case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
2505                 challenges = getResponseHeaderGroup().getHeaders(HttpAuthenticator.PROXY_AUTH);
2506                 realmsUsed = proxyRealms;
2507                 host = conn.getProxyHost();
2508                 break;
2509         }
2510         boolean authenticated = false;
2511         // if there was a header requesting authentication
2512         if (challenges.length > 0) {
2513             AuthScheme authscheme = null;
2514             try {
2515                 authscheme = HttpAuthenticator.selectAuthScheme(challenges);
2516             } catch (MalformedChallengeException e) {
2517                 if (LOG.isErrorEnabled()) {
2518                     LOG.error(e.getMessage(), e);
2519                 }
2520                 return true;
2521             } catch (UnsupportedOperationException e) {
2522                 if (LOG.isErrorEnabled()) {
2523                     LOG.error(e.getMessage(), e);
2524                 }
2525                 return true;
2526             }
2527         
2528             StringBuffer buffer = new StringBuffer();
2529             buffer.append(host);
2530             buffer.append('#');
2531             buffer.append(authscheme.getID());
2532             String realm = buffer.toString();
2533 
2534             if (realmsUsed.contains(realm)) {
2535                 if (LOG.isInfoEnabled()) {
2536                     buffer = new StringBuffer();
2537                     buffer.append("Already tried to authenticate with '");
2538                     buffer.append(authscheme.getRealm());
2539                     buffer.append("' authentication realm at ");
2540                     buffer.append(host);
2541                     buffer.append(", but still receiving: ");
2542                     buffer.append(statusLine.toString());
2543                     LOG.info(buffer.toString());
2544                 }
2545                 return true;
2546             } else {
2547                 realmsUsed.add(realm);
2548             }
2549 
2550             try {
2551                 //remove preemptive header and reauthenticate
2552                 switch (statusCode) {
2553                     case HttpStatus.SC_UNAUTHORIZED:
2554                         removeRequestHeader(HttpAuthenticator.WWW_AUTH_RESP);
2555                         authenticated = HttpAuthenticator.authenticate(
2556                             authscheme, this, conn, state);
2557                         this.realm = authscheme.getRealm();
2558                         this.authScheme = authscheme;
2559                         break;
2560                     case HttpStatus.SC_PROXY_AUTHENTICATION_REQUIRED:
2561                         removeRequestHeader(HttpAuthenticator.PROXY_AUTH_RESP);
2562                         authenticated = HttpAuthenticator.authenticateProxy(
2563                             authscheme, this, conn, state);
2564                         this.proxyRealm = authscheme.getRealm();
2565                         this.proxyAuthScheme = authscheme;
2566                         break;
2567                 }
2568             } catch (AuthenticationException e) {
2569                 LOG.warn(e.getMessage());
2570                 return true; // finished request
2571             }
2572             if (!authenticated) {
2573                 // won't be able to authenticate to this challenge
2574                 // without additional information
2575                 LOG.debug("HttpMethodBase.execute(): Server demands "
2576                           + "authentication credentials, but none are "
2577                           + "available, so aborting.");
2578             } else {
2579                 LOG.debug("HttpMethodBase.execute(): Server demanded "
2580                           + "authentication credentials, will try again.");
2581                 // let's try it again, using the credentials
2582             }
2583         }
2584 
2585         return !authenticated; // finished processing if we aren't authenticated
2586     }
2587 
2588     /***
2589      * Returns proxy authentication realm, if it has been used during authentication process. 
2590      * Otherwise returns <tt>null</tt>.
2591      * 
2592      * @return proxy authentication realm
2593      */
2594     public String getProxyAuthenticationRealm() {
2595         return this.proxyRealm;
2596     }
2597 
2598     /***
2599      * Returns authentication realm, if it has been used during authentication process. 
2600      * Otherwise returns <tt>null</tt>.
2601      * 
2602      * @return authentication realm
2603      */
2604     public String getAuthenticationRealm() {
2605         return this.realm;
2606     }
2607 
2608     /***
2609      * Sends the request and reads the response. The request will be retried 
2610      * {@link #maxRetries} times if the operation fails with a
2611      * {@link HttpRecoverableException}.
2612      *
2613      * <p>
2614      * The {@link #isUsed()} is set to true if the write succeeds.
2615      * </p>
2616      *
2617      * @param state the {@link HttpState state} information associated with this method
2618      * @param conn the {@link HttpConnection connection} used to execute
2619      *        this HTTP method
2620      *
2621      * @throws IOException if an I/O (transport) error occurs
2622      * @throws HttpException  if a protocol exception occurs.
2623      * @throws HttpRecoverableException if a recoverable transport error occurs. 
2624      *                    Usually this kind of exceptions can be recovered from by
2625      *                    retrying the HTTP method 
2626      *
2627      * @see #writeRequest(HttpState,HttpConnection)
2628      * @see #readResponse(HttpState,HttpConnection)
2629      */
2630     private void processRequest(HttpState state, HttpConnection connection)
2631     throws HttpException, IOException {
2632         LOG.trace("enter HttpMethodBase.processRequest(HttpState, HttpConnection)");
2633 
2634         int execCount = 0;
2635         boolean requestSent = false;
2636         
2637         // loop until the method is successfully processed, the retryHandler 
2638         // returns false or a non-recoverable exception is thrown
2639         while (true) {
2640             execCount++;
2641             requestSent = false;
2642             
2643             if (LOG.isTraceEnabled()) {
2644                 LOG.trace("Attempt number " + execCount + " to process request");
2645             }
2646             try {
2647                 if (!connection.isOpen()) {
2648                     LOG.debug("Opening the connection.");
2649                     connection.open();
2650                 }
2651                 writeRequest(state, connection);
2652                 requestSent = true;
2653                 readResponse(state, connection);
2654                 // the method has successfully executed
2655                 used = true; 
2656                 break;
2657             } catch (HttpRecoverableException httpre) {
2658                 if (LOG.isDebugEnabled()) {
2659                     LOG.debug("Closing the connection.");
2660                 }
2661                 connection.close();
2662                 LOG.info("Recoverable exception caught when processing request");
2663                 // update the recoverable exception count.
2664                 recoverableExceptionCount++;
2665                 
2666                 // test if this method should be retried                
2667                 if (!getMethodRetryHandler().retryMethod(
2668                         this, 
2669                         connection, 
2670                         httpre, 
2671                         execCount, 
2672                         requestSent)
2673                 ) {
2674                     LOG.warn(
2675                         "Recoverable exception caught but MethodRetryHandler.retryMethod() "
2676                         + "returned false, rethrowing exception"
2677                     );
2678                     // this connection can no longer be used, it has been closed
2679                     doneWithConnection = true;
2680                     throw httpre;
2681                 }
2682             } catch (IOException e) {
2683                 connection.close();
2684                 doneWithConnection = true;
2685                 throw e;
2686             } catch (RuntimeException e) {
2687                 connection.close();
2688                 doneWithConnection = true;
2689                 throw e;
2690             }
2691         }
2692     }
2693 
2694     /***
2695      * Returns the character set from the <tt>Content-Type</tt> header.
2696      * @param contentheader The content header.
2697      * @return String The character set.
2698      */
2699     protected static String getContentCharSet(Header contentheader) {
2700         LOG.trace("enter getContentCharSet( Header contentheader )");
2701         String charset = null;
2702         if (contentheader != null) {
2703             try {
2704                 HeaderElement values[] = contentheader.getValues();
2705                 // I expect only one header element to be there
2706                 // No more. no less
2707                 if (values.length == 1) {
2708                     NameValuePair param = values[0].getParameterByName("charset");
2709                     if (param != null) {
2710                         // If I get anything "funny" 
2711                         // UnsupportedEncondingException will result
2712                         charset = param.getValue();
2713                     }
2714                 }
2715             } catch (HttpException e) {
2716                 LOG.error(e);
2717             }
2718         }
2719         if (charset == null) {
2720             if (LOG.isDebugEnabled()) {
2721                 LOG.debug("Default charset used: " + HttpConstants.DEFAULT_CONTENT_CHARSET);
2722             }
2723             charset = HttpConstants.DEFAULT_CONTENT_CHARSET;
2724         }
2725         return charset;
2726     }
2727 
2728 
2729     /***
2730      * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
2731      * 
2732      * @return String The character set.
2733      */
2734     public String getRequestCharSet() {
2735         return getContentCharSet(getRequestHeader("Content-Type"));
2736     }
2737 
2738 
2739     /***  
2740      * Returns the character encoding of the response from the <tt>Content-Type</tt> header.
2741      * 
2742      * @return String The character set.
2743      */
2744     public String getResponseCharSet() {
2745         return getContentCharSet(getResponseHeader("Content-Type"));
2746     }
2747 
2748     /***
2749      * Returns the number of "recoverable" exceptions thrown and handled, to
2750      * allow for monitoring the quality of the connection.
2751      *
2752      * @return The number of recoverable exceptions handled by the method.
2753      */
2754     public int getRecoverableExceptionCount() {
2755         return recoverableExceptionCount;
2756     }
2757 
2758     /***
2759      * A response has been consumed.
2760      *
2761      * <p>The default behavior for this class is to check to see if the connection
2762      * should be closed, and close if need be, and to ensure that the connection
2763      * is returned to the connection manager - if and only if we are not still
2764      * inside the execute call.</p>
2765      *
2766      */
2767     protected void responseBodyConsumed() {
2768 
2769         // make sure this is the initial invocation of the notification,
2770         // ignore subsequent ones.
2771         responseStream = null;
2772         if (responseConnection != null) {
2773             responseConnection.setLastResponseInputStream(null);
2774 
2775             if (shouldCloseConnection(responseConnection)) {
2776                 responseConnection.close();
2777             }
2778         }
2779         this.connectionCloseForced = false;
2780         doneWithConnection = true;
2781         if (!inExecute) {
2782             ensureConnectionRelease();
2783         }
2784     }
2785 
2786     /***
2787      * Insure that the connection is released back to the pool.
2788      */
2789     private void ensureConnectionRelease() {
2790         if (responseConnection != null) {
2791             responseConnection.releaseConnection();
2792             responseConnection = null;
2793         }
2794     }
2795 
2796     /***
2797      * Returns the {@link HostConfiguration host configuration}.
2798      * 
2799      * @return the host configuration
2800      */
2801     public HostConfiguration getHostConfiguration() {
2802         return hostConfiguration;
2803     }
2804 
2805     /***
2806      * Sets the {@link HostConfiguration host configuration}.
2807      * 
2808      * @param hostConfiguration The hostConfiguration to set
2809      */
2810     public void setHostConfiguration(HostConfiguration hostConfiguration) {
2811         this.hostConfiguration = hostConfiguration;
2812     }
2813 
2814     /***
2815      * Returns the {@link MethodRetryHandler retry handler} for this HTTP method
2816      * 
2817      * @return the methodRetryHandler
2818      */
2819     public MethodRetryHandler getMethodRetryHandler() {
2820         
2821         if (methodRetryHandler == null) {
2822             methodRetryHandler = new DefaultMethodRetryHandler();
2823         }
2824 
2825         return methodRetryHandler;
2826     }
2827 
2828     /***
2829      * Sets the {@link MethodRetryHandler retry handler} for this HTTP method
2830      * 
2831      * @param handler the methodRetryHandler to use when this method executed
2832      */
2833     public void setMethodRetryHandler(MethodRetryHandler handler) {
2834         methodRetryHandler = handler;
2835     }
2836 
2837     /***
2838      * This method is a dirty hack intended to work around 
2839      * current (2.0) design flaw that prevents the user from
2840      * obtaining correct status code, headers and response body from the 
2841      * preceding HTTP CONNECT method.
2842      * 
2843      * TODO: Remove this crap as soon as possible
2844      */
2845     protected void fakeResponse(
2846         StatusLine statusline, 
2847         HeaderGroup responseheaders,
2848         InputStream responseStream
2849     ) {
2850         // set used so that the response can be read
2851         this.used = true;
2852         this.statusLine = statusline;
2853         this.responseHeaders = responseheaders;
2854         this.responseBody = null;
2855         this.responseStream = responseStream;
2856     }
2857 }