View Javadoc

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