KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > apache > commons > httpclient > HttpMethodBase


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 JavaDoc;
35 import java.io.ByteArrayOutputStream JavaDoc;
36 import java.io.IOException JavaDoc;
37 import java.io.InputStream JavaDoc;
38 import java.io.InterruptedIOException JavaDoc;
39 import java.util.HashSet JavaDoc;
40 import java.util.Set JavaDoc;
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 JavaDoc agent = null;
126         try {
127             agent = System.getProperty("httpclient.useragent");
128         } catch (SecurityException JavaDoc 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 JavaDoc realms = null;
155
156     /** Actual authentication realm */
157     private String JavaDoc 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 JavaDoc proxyRealms = null;
164
165     /** Actual proxy authentication realm */
166     private String JavaDoc proxyRealm = null;
167
168     /** Path of the HTTP method. */
169     private String JavaDoc path = null;
170
171     /** Query string of the HTTP method, if any. */
172     private String JavaDoc 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 JavaDoc 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 JavaDoc uri)
246         throws IllegalArgumentException JavaDoc, IllegalStateException JavaDoc {
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 JavaDoc("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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc headerName, String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc instream = getResponseBodyAsStream();
680                 if (instream != null) {
681                     LOG.debug("Buffering response body");
682                     ByteArrayOutputStream JavaDoc outstream = new ByteArrayOutputStream JavaDoc();
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 JavaDoc 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 JavaDoc getResponseBodyAsStream() throws IOException JavaDoc {
710         if (responseStream != null) {
711             return responseStream;
712         }
713         if (responseBody != null) {
714             InputStream JavaDoc byteResponseStream = new ByteArrayInputStream JavaDoc(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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc headerName, String JavaDoc 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 JavaDoc("HttpState parameter may not be null");
970         }
971         if (conn == null) {
972             throw new IllegalArgumentException JavaDoc("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 JavaDoc("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 JavaDoc {
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 JavaDoc();
1049            proxyRealms = new HashSet JavaDoc();
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 JavaDoc 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 JavaDoc oldProtocol = currentUri.getScheme();
1205        String JavaDoc 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 JavaDoc oldHost = currentUri.getHost();
1213            String JavaDoc 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 JavaDoc protocol) {
1250        String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc, 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 JavaDoc, 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 JavaDoc, 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 JavaDoc, 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 JavaDoc 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 JavaDoc, 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 JavaDoc, 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 JavaDoc, 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 JavaDoc, 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 JavaDoc {
1682        if (used) {
1683            throw new IllegalStateException JavaDoc("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 JavaDoc {
1695        if (!used) {
1696            throw new IllegalStateException JavaDoc("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 JavaDoc generateRequestLine(HttpConnection connection,
1715        String JavaDoc name, String JavaDoc requestPath, String JavaDoc query, String JavaDoc version) {
1716        LOG.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
1717            + "String, String, String, String)");
1718
1719        StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
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 JavaDoc 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 JavaDoc, 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 JavaDoc 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 JavaDoc readResponseBody(HttpConnection conn)
2009        throws IOException JavaDoc {
2010
2011        LOG.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
2012
2013        responseBody = null; // is this desired?
2014
InputStream JavaDoc is = conn.getResponseInputStream();
2015        if (Wire.CONTENT_WIRE.enabled()) {
2016            is = new WireLogInputStream(is, Wire.CONTENT_WIRE);
2017        }
2018        InputStream JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc, 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 JavaDoc, HttpRecoverableException, HttpException {
2160        LOG.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
2161
2162        //read out the HTTP status string
2163
String JavaDoc 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 JavaDoc 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 JavaDoc, 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 JavaDoc 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 JavaDoc 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 JavaDoc, 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 JavaDoc, 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 JavaDoc 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 JavaDoc, HttpException {
2396        LOG.trace(
2397            "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
2398        String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc realmsUsed = null;
2475        String JavaDoc 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 JavaDoc e) {
2503                if (LOG.isErrorEnabled()) {
2504                    LOG.error(e.getMessage(), e);
2505                }
2506                return true;
2507            }
2508        
2509            StringBuffer JavaDoc buffer = new StringBuffer JavaDoc();
2510            buffer.append(host);
2511            buffer.append('#');
2512            buffer.append(authscheme.getID());
2513            String JavaDoc realm = buffer.toString();
2514
2515            if (realmsUsed.contains(realm)) {
2516                if (LOG.isInfoEnabled()) {
2517                    buffer = new StringBuffer JavaDoc();
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 JavaDoc 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 JavaDoc 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 JavaDoc {
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 JavaDoc e) {
2664                connection.close();
2665                doneWithConnection = true;
2666                throw e;
2667            } catch (RuntimeException JavaDoc 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 JavaDoc getContentCharSet(Header contentheader) {
2681        LOG.trace("enter getContentCharSet( Header contentheader )");
2682        String JavaDoc 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 JavaDoc 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 JavaDoc 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 JavaDoc 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}
2839
Popular Tags