View Javadoc

1   /*
2    * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/HttpMethodBase.java,v 1.207 2004/05/13 04:03:24 mbecke Exp $
3    * $Revision: 1.207 $
4    * $Date: 2004/05/13 04:03:24 $
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   */
29  
30  package org.apache.commons.httpclient;
31  
32  import java.io.ByteArrayInputStream;
33  import java.io.ByteArrayOutputStream;
34  import java.io.IOException;
35  import java.io.InputStream;
36  
37  import org.apache.commons.httpclient.auth.AuthState;
38  import org.apache.commons.httpclient.cookie.CookiePolicy;
39  import org.apache.commons.httpclient.cookie.CookieSpec;
40  import org.apache.commons.httpclient.cookie.MalformedCookieException;
41  import org.apache.commons.httpclient.params.HttpMethodParams;
42  import org.apache.commons.httpclient.protocol.Protocol;
43  import org.apache.commons.httpclient.util.EncodingUtil;
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  
47  /***
48   * An abstract base implementation of HttpMethod.
49   * <p>
50   * At minimum, subclasses will need to override:
51   * <ul>
52   *   <li>{@link #getName} to return the approriate name for this method
53   *   </li>
54   * </ul>
55   * </p>
56   *
57   * <p>
58   * When a method requires additional request headers, subclasses will typically
59   * want to override:
60   * <ul>
61   *   <li>{@link #addRequestHeaders addRequestHeaders(HttpState,HttpConnection)}
62   *      to write those headers
63   *   </li>
64   * </ul>
65   * </p>
66   *
67   * <p>
68   * When a method expects specific response headers, subclasses may want to
69   * override:
70   * <ul>
71   *   <li>{@link #processResponseHeaders processResponseHeaders(HttpState,HttpConnection)}
72   *     to handle those headers
73   *   </li>
74   * </ul>
75   * </p>
76   *
77   *
78   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
79   * @author Rodney Waldhoff
80   * @author Sean C. Sullivan
81   * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
82   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
83   * @author <a href="mailto:dims@apache.org">Davanum Srinivas</a>
84   * @author Ortwin Glueck
85   * @author Eric Johnson
86   * @author Michael Becke
87   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
88   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
89   * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
90   * @author Christian Kohlschuetter
91   *
92   * @version $Revision: 1.207 $ $Date: 2004/05/13 04:03:24 $
93   */
94  public abstract class HttpMethodBase implements HttpMethod {
95  
96      // -------------------------------------------------------------- Constants
97  
98      /*** Log object for this class. */
99      private static final Log LOG = LogFactory.getLog(HttpMethodBase.class);
100 
101     // ----------------------------------------------------- Instance variables 
102 
103     /*** Request headers, if any. */
104     private HeaderGroup requestHeaders = new HeaderGroup();
105 
106     /*** The Status-Line from the response. */
107     private StatusLine statusLine = null;
108 
109     /*** Response headers, if any. */
110     private HeaderGroup responseHeaders = new HeaderGroup();
111 
112     /*** Response trailer headers, if any. */
113     private HeaderGroup responseTrailerHeaders = new HeaderGroup();
114 
115     /*** Path of the HTTP method. */
116     private String path = null;
117 
118     /*** Query string of the HTTP method, if any. */
119     private String queryString = null;
120 
121     /*** The response body of the HTTP method, assuming it has not be 
122      * intercepted by a sub-class. */
123     private InputStream responseStream = null;
124 
125     /*** The connection that the response stream was read from. */
126     private HttpConnection responseConnection = null;
127 
128     /*** Buffer for the response */
129     private byte[] responseBody = null;
130 
131     /*** True if the HTTP method should automatically follow HTTP redirects.*/
132     private boolean followRedirects = false;
133 
134     /*** True if the HTTP method should automatically handle
135     *  HTTP authentication challenges. */
136     private boolean doAuthentication = true;
137 
138     /*** HTTP protocol parameters. */
139     private HttpMethodParams params = new HttpMethodParams();
140 
141     /*** Host authentication state */
142     private AuthState hostAuthState = new AuthState();
143 
144     /*** Proxy authentication state */
145     private AuthState proxyAuthState = new AuthState();
146 
147     /*** True if this method has already been executed. */
148     private boolean used = false;
149 
150     /*** Count of how many times did this HTTP method transparently handle 
151     * a recoverable exception. */
152     private int recoverableExceptionCount = 0;
153 
154     /*** the host configuration for this HTTP method, can be null */
155     private HostConfiguration hostConfiguration;
156 
157     /***
158      * Handles method retries
159      */
160     private MethodRetryHandler methodRetryHandler;
161 
162     /*** True if the connection must be closed when no longer needed */
163     private boolean connectionCloseForced = false;
164 
165     /*** Number of milliseconds to wait for 100-contunue response. */
166     private static final int RESPONSE_WAIT_TIME_MS = 3000;
167 
168     /*** HTTP protocol version used for execution of this method. */
169     private HttpVersion effectiveVersion = null;
170 
171     /*** Whether the execution of this method has been aborted */
172     private transient boolean aborted = false;
173 
174     // ----------------------------------------------------------- Constructors
175 
176     /***
177      * No-arg constructor.
178      */
179     public HttpMethodBase() {
180     }
181 
182     /***
183      * Constructor specifying a URI.
184      * It is responsibility of the caller to ensure that URI elements
185      * (path & query parameters) are properly encoded (URL safe).
186      *
187      * @param uri either an absolute or relative URI. The URI is expected
188      *            to be URL-encoded
189      * 
190      * @throws IllegalArgumentException when URI is invalid
191      * @throws IllegalStateException when protocol of the absolute URI is not recognised
192      */
193     public HttpMethodBase(String uri) 
194         throws IllegalArgumentException, IllegalStateException {
195 
196         try {
197 
198             // create a URI and allow for null/empty uri values
199             if (uri == null || uri.equals("")) {
200                 uri = "/";
201             }
202             setURI(new URI(uri, true));
203         } catch (URIException e) {
204             throw new IllegalArgumentException("Invalid uri '" 
205                 + uri + "': " + e.getMessage() 
206             );
207         }
208     }
209 
210     // ------------------------------------------- Property Setters and Getters
211 
212     /***
213      * Obtains the name of the HTTP method as used in the HTTP request line,
214      * for example <tt>"GET"</tt> or <tt>"POST"</tt>.
215      * 
216      * @return the name of this method
217      */
218     public abstract String getName();
219 
220     /***
221      * Returns the URI of the HTTP method
222      * 
223      * @return The URI
224      * 
225      * @throws URIException If the URI cannot be created.
226      * 
227      * @see org.apache.commons.httpclient.HttpMethod#getURI()
228      */
229     public URI getURI() throws URIException {
230 
231         if (hostConfiguration == null) {
232             // just use a relative URI, the host hasn't been set
233             URI tmpUri = new URI(null, null, path, null, null);
234             tmpUri.setEscapedQuery(queryString);
235             return tmpUri;
236         } else {
237 
238             // we only want to include the port if it's not the default
239             int port = hostConfiguration.getPort();
240             if (port == hostConfiguration.getProtocol().getDefaultPort()) {
241                 port = -1;
242             }
243 
244             URI tmpUri = new URI(
245                 hostConfiguration.getProtocol().getScheme(),
246                 null,
247                 hostConfiguration.getHost(),
248                 port,
249                 path,
250                 null // to set an escaped form
251             );
252             tmpUri.setEscapedQuery(queryString);
253             return tmpUri;
254 
255         }
256 
257     }
258 
259     /***
260      * Sets the URI for this method. 
261      * 
262      * @param uri URI to be set 
263      * 
264      * @throws URIException if a URI cannot be set
265      * 
266      * @since 3.0
267      */
268     public void setURI(URI uri) throws URIException {
269         // only set the host if specified by the URI
270         if (uri.isAbsoluteURI()) {
271             if (this.hostConfiguration == null) {
272                 this.hostConfiguration = new HostConfiguration();
273             }
274             this.hostConfiguration.setHost(
275                 uri.getHost(),
276                 uri.getPort(),
277                 uri.getScheme()
278             ); 
279         }
280         
281         // set the path, defaulting to root
282         setPath(
283             uri.getPath() == null
284             ? "/"
285             : uri.getEscapedPath()
286         );
287         setQueryString(uri.getEscapedQuery());
288     } 
289 
290     /***
291      * Sets whether or not the HTTP method should automatically follow HTTP redirects 
292      * (status code 302, etc.)
293      * 
294      * @param followRedirects <tt>true</tt> if the method will automatically follow redirects,
295      * <tt>false</tt> otherwise.
296      */
297     public void setFollowRedirects(boolean followRedirects) {
298         this.followRedirects = followRedirects;
299     }
300 
301     /***
302      * Returns <tt>true</tt> if the HTTP method should automatically follow HTTP redirects 
303      * (status code 302, etc.), <tt>false</tt> otherwise.
304      * 
305      * @return <tt>true</tt> if the method will automatically follow HTTP redirects, 
306      * <tt>false</tt> otherwise.
307      */
308     public boolean getFollowRedirects() {
309         return this.followRedirects;
310     }
311 
312     /*** Sets whether version 1.1 of the HTTP protocol should be used per default.
313      *
314      * @param http11 <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
315      * 
316      * @deprecated Use {@link HttpMethodParams#setVersion(HttpVersion)}
317      */
318     public void setHttp11(boolean http11) {
319         if (http11) {
320             this.params.setVersion(HttpVersion.HTTP_1_1);
321         } else {
322             this.params.setVersion(HttpVersion.HTTP_1_0);
323         } 
324     }
325 
326     /***
327      * Returns <tt>true</tt> if the HTTP method should automatically handle HTTP 
328      * authentication challenges (status code 401, etc.), <tt>false</tt> otherwise
329      *
330      * @return <tt>true</tt> if authentication challenges will be processed 
331      * automatically, <tt>false</tt> otherwise.
332      * 
333      * @since 2.0
334      */
335     public boolean getDoAuthentication() {
336         return doAuthentication;
337     }
338 
339     /***
340      * Sets whether or not the HTTP method should automatically handle HTTP 
341      * authentication challenges (status code 401, etc.)
342      *
343      * @param doAuthentication <tt>true</tt> to process authentication challenges
344      * authomatically, <tt>false</tt> otherwise.
345      * 
346      * @since 2.0
347      */
348     public void setDoAuthentication(boolean doAuthentication) {
349         this.doAuthentication = doAuthentication;
350     }
351 
352     // ---------------------------------------------- Protected Utility Methods
353 
354     /***
355      * Returns <tt>true</tt> if version 1.1 of the HTTP protocol should be 
356      * used per default, <tt>false</tt> if version 1.0 should be used.
357      *
358      * @return <tt>true</tt> to use HTTP/1.1, <tt>false</tt> to use 1.0
359      * 
360      * @deprecated Use {@link HttpMethodParams#getVersion()}
361      */
362     public boolean isHttp11() {
363         return this.params.getVersion().equals(HttpVersion.HTTP_1_1);
364     }
365 
366     /***
367      * Sets the path of the HTTP method.
368      * It is responsibility of the caller to ensure that the path is
369      * properly encoded (URL safe).
370      *
371      * @param path the path of the HTTP method. The path is expected
372      *        to be URL-encoded
373      */
374     public void setPath(String path) {
375         this.path = path;
376     }
377 
378     /***
379      * Adds the specified request header, NOT overwriting any previous value.
380      * Note that header-name matching is case insensitive.
381      *
382      * @param header the header to add to the request
383      */
384     public void addRequestHeader(Header header) {
385         LOG.trace("HttpMethodBase.addRequestHeader(Header)");
386 
387         if (header == null) {
388             LOG.debug("null header value ignored");
389         } else {
390             getRequestHeaderGroup().addHeader(header);
391         }
392     }
393 
394     /***
395      * Use this method internally to add footers.
396      * 
397      * @param footer The footer to add.
398      */
399     public void addResponseFooter(Header footer) {
400         getResponseTrailerHeaderGroup().addHeader(footer);
401     }
402 
403     /***
404      * Gets the path of this HTTP method.
405      * Calling this method <em>after</em> the request has been executed will 
406      * return the <em>actual</em> path, following any redirects automatically
407      * handled by this HTTP method.
408      *
409      * @return the path to request or "/" if the path is blank.
410      */
411     public String getPath() {
412         return (path == null || path.equals("")) ? "/" : path;
413     }
414 
415     /***
416      * Sets the query string of this HTTP method. The caller must ensure that the string 
417      * is properly URL encoded. The query string should not start with the question 
418      * mark character.
419      *
420      * @param queryString the query string
421      * 
422      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
423      */
424     public void setQueryString(String queryString) {
425         this.queryString = queryString;
426     }
427 
428     /***
429      * Sets the query string of this HTTP method.  The pairs are encoded as UTF-8 characters.  
430      * To use a different charset the parameters can be encoded manually using EncodingUtil 
431      * and set as a single String.
432      *
433      * @param params an array of {@link NameValuePair}s to add as query string
434      *        parameters. The name/value pairs will be automcatically 
435      *        URL encoded
436      * 
437      * @see EncodingUtil#formUrlEncode(NameValuePair[], String)
438      * @see #setQueryString(String)
439      */
440     public void setQueryString(NameValuePair[] params) {
441         LOG.trace("enter HttpMethodBase.setQueryString(NameValuePair[])");
442         queryString = EncodingUtil.formUrlEncode(params, "UTF-8");
443     }
444 
445     /***
446      * Gets the query string of this HTTP method.
447      *
448      * @return The query string
449      */
450     public String getQueryString() {
451         return queryString;
452     }
453 
454     /***
455      * Set the specified request header, overwriting any previous value. Note
456      * that header-name matching is case-insensitive.
457      *
458      * @param headerName the header's name
459      * @param headerValue the header's value
460      */
461     public void setRequestHeader(String headerName, String headerValue) {
462         Header header = new Header(headerName, headerValue);
463         setRequestHeader(header);
464     }
465 
466     /***
467      * Sets the specified request header, overwriting any previous value.
468      * Note that header-name matching is case insensitive.
469      * 
470      * @param header the header
471      */
472     public void setRequestHeader(Header header) {
473         
474         Header[] headers = getRequestHeaderGroup().getHeaders(header.getName());
475         
476         for (int i = 0; i < headers.length; i++) {
477             getRequestHeaderGroup().removeHeader(headers[i]);
478         }
479         
480         getRequestHeaderGroup().addHeader(header);
481         
482     }
483 
484     /***
485      * Returns the specified request header. Note that header-name matching is
486      * case insensitive. <tt>null</tt> will be returned if either
487      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
488      * <i>headerName</i>.
489      * 
490      * @param headerName The name of the header to be returned.
491      *
492      * @return The specified request header.
493      * 
494      * @since 3.0
495      */
496     public Header getRequestHeader(String headerName) {
497         if (headerName == null) {
498             return null;
499         } else {
500             return getRequestHeaderGroup().getCondensedHeader(headerName);
501         }
502     }
503 
504     /***
505      * Returns an array of the requests headers that the HTTP method currently has
506      *
507      * @return an array of my request headers.
508      */
509     public Header[] getRequestHeaders() {
510         return getRequestHeaderGroup().getAllHeaders();
511     }
512 
513     /***
514      * @see org.apache.commons.httpclient.HttpMethod#getRequestHeaders(java.lang.String)
515      */
516     public Header[] getRequestHeaders(String headerName) {
517         return getRequestHeaderGroup().getHeaders(headerName);
518     }
519 
520     /***
521      * Gets the {@link HeaderGroup header group} storing the request headers.
522      * 
523      * @return a HeaderGroup
524      * 
525      * @since 2.0beta1
526      */
527     protected HeaderGroup getRequestHeaderGroup() {
528         return requestHeaders;
529     }
530 
531     /***
532      * Gets the {@link HeaderGroup header group} storing the response trailer headers 
533      * as per RFC 2616 section 3.6.1.
534      * 
535      * @return a HeaderGroup
536      * 
537      * @since 2.0beta1
538      */
539     protected HeaderGroup getResponseTrailerHeaderGroup() {
540         return responseTrailerHeaders;
541     }
542 
543     /***
544      * Gets the {@link HeaderGroup header group} storing the response headers.
545      * 
546      * @return a HeaderGroup
547      * 
548      * @since 2.0beta1
549      */
550     protected HeaderGroup getResponseHeaderGroup() {
551         return responseHeaders;
552     }
553     
554     /***
555      * @see org.apache.commons.httpclient.HttpMethod#getResponseHeaders(java.lang.String)
556      * 
557      * @since 3.0
558      */
559     public Header[] getResponseHeaders(String headerName) {
560         return getResponseHeaderGroup().getHeaders(headerName);
561     }
562 
563     /***
564      * Returns the response status code.
565      *
566      * @return the status code associated with the latest response.
567      */
568     public int getStatusCode() {
569         return statusLine.getStatusCode();
570     }
571 
572     /***
573      * Provides access to the response status line.
574      *
575      * @return the status line object from the latest response.
576      * @since 2.0
577      */
578     public StatusLine getStatusLine() {
579         return statusLine;
580     }
581 
582     /***
583      * Checks if response data is available.
584      * @return <tt>true</tt> if response data is available, <tt>false</tt> otherwise.
585      */
586     private boolean responseAvailable() {
587         return (responseBody != null) || (responseStream != null);
588     }
589 
590     /***
591      * Returns an array of the response headers that the HTTP method currently has
592      * in the order in which they were read.
593      *
594      * @return an array of response headers.
595      */
596     public Header[] getResponseHeaders() {
597         return getResponseHeaderGroup().getAllHeaders();
598     }
599 
600     /***
601      * Gets the response header associated with the given name. Header name
602      * matching is case insensitive. <tt>null</tt> will be returned if either
603      * <i>headerName</i> is <tt>null</tt> or there is no matching header for
604      * <i>headerName</i>.
605      *
606      * @param headerName the header name to match
607      *
608      * @return the matching header
609      */
610     public Header getResponseHeader(String headerName) {        
611         if (headerName == null) {
612             return null;
613         } else {
614             return getResponseHeaderGroup().getCondensedHeader(headerName);
615         }        
616     }
617 
618 
619     /***
620      * Return the length (in bytes) of the response body, as specified in a
621      * <tt>Content-Length</tt> header.
622      *
623      * <p>
624      * Return <tt>-1</tt> when the content-length is unknown.
625      * </p>
626      *
627      * @return content length, if <tt>Content-Length</tt> header is available. 
628      *          <tt>0</tt> indicates that the request has no body.
629      *          If <tt>Content-Length</tt> header is not present, the method 
630      *          returns  <tt>-1</tt>.
631      */
632     public long getResponseContentLength() {
633         Header[] headers = getResponseHeaderGroup().getHeaders("Content-Length");
634         if (headers.length == 0) {
635             return -1;
636         }
637         if (headers.length > 1) {
638             LOG.warn("Multiple content-length headers detected");
639         }
640         for (int i = headers.length - 1; i >= 0; i--) {
641             Header header = headers[i];
642             try {
643                 return Long.parseLong(header.getValue());
644             } catch (NumberFormatException e) {
645                 if (LOG.isWarnEnabled()) {
646                     LOG.warn("Invalid content-length value: " + e.getMessage());
647                 }
648             }
649             // See if we can have better luck with another header, if present
650         }
651         return -1;
652     }
653 
654 
655     /***
656      * Returns the response body of the HTTP method, if any, as an array of bytes.
657      * If response body is not available or cannot be read, returns <tt>null</tt>
658      * 
659      * @return The response body.
660      * 
661      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
662      * response body.
663      */
664     public byte[] getResponseBody() throws IOException {
665         if (this.responseBody == null) {
666             InputStream instream = getResponseBodyAsStream();
667             if (instream != null) {
668                 LOG.debug("Buffering response body");
669                 ByteArrayOutputStream outstream = new ByteArrayOutputStream();
670                 byte[] buffer = new byte[4096];
671                 int len;
672                 while ((len = instream.read(buffer)) > 0) {
673                     outstream.write(buffer, 0, len);
674                 }
675                 outstream.close();
676                 setResponseStream(null);
677                 this.responseBody = outstream.toByteArray();
678             }
679         }
680         return this.responseBody;
681     }
682 
683     /***
684      * Returns the response body of the HTTP method, if any, as an {@link InputStream}. 
685      * If response body is not available, returns <tt>null</tt>
686      * 
687      * @return The response body
688      * 
689      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
690      * response body.
691      */
692     public InputStream getResponseBodyAsStream() throws IOException {
693         if (responseStream != null) {
694             return responseStream;
695         }
696         if (responseBody != null) {
697             InputStream byteResponseStream = new ByteArrayInputStream(responseBody);
698             LOG.debug("re-creating response stream from byte array");
699             return byteResponseStream;
700         }
701         return null;
702     }
703 
704     /***
705      * Returns the response body of the HTTP method, if any, as a {@link String}. 
706      * If response body is not available or cannot be read, returns <tt>null</tt>
707      * The string conversion on the data is done using the character encoding specified
708      * in <tt>Content-Type</tt> header.
709      *
710      * @return The response body.
711      * 
712      * @throws IOException If an I/O (transport) problem occurs while obtaining the 
713      * response body.
714      */
715     public String getResponseBodyAsString() throws IOException {
716         byte[] rawdata = null;
717         if (responseAvailable()) {
718             rawdata = getResponseBody();
719         }
720         if (rawdata != null) {
721             return EncodingUtil.getString(rawdata, getResponseCharSet());
722         } else {
723             return null;
724         }
725     }
726 
727     /***
728      * Returns an array of the response footers that the HTTP method currently has
729      * in the order in which they were read.
730      *
731      * @return an array of footers
732      */
733     public Header[] getResponseFooters() {
734         return getResponseTrailerHeaderGroup().getAllHeaders();
735     }
736 
737     /***
738      * Gets the response footer associated with the given name.
739      * Footer name matching is case insensitive.
740      * <tt>null</tt> will be returned if either <i>footerName</i> is
741      * <tt>null</tt> or there is no matching footer for <i>footerName</i>
742      * or there are no footers available.  If there are multiple footers
743      * with the same name, there values will be combined with the ',' separator
744      * as specified by RFC2616.
745      * 
746      * @param footerName the footer name to match
747      * @return the matching footer
748      */
749     public Header getResponseFooter(String footerName) {
750         if (footerName == null) {
751             return null;
752         } else {
753             return getResponseTrailerHeaderGroup().getCondensedHeader(footerName);
754         }
755     }
756 
757     /***
758      * Sets the response stream.
759      * @param responseStream The new response stream.
760      */
761     protected void setResponseStream(InputStream responseStream) {
762         this.responseStream = responseStream;
763     }
764 
765     /***
766      * Returns a stream from which the body of the current response may be read.
767      * If the method has not yet been executed, if <code>responseBodyConsumed</code>
768      * has been called, or if the stream returned by a previous call has been closed,
769      * <code>null</code> will be returned.
770      *
771      * @return the current response stream
772      */
773     protected InputStream getResponseStream() {
774         return responseStream;
775     }
776     
777     /***
778      * Returns the status text (or "reason phrase") associated with the latest
779      * response.
780      * 
781      * @return The status text.
782      */
783     public String getStatusText() {
784         return statusLine.getReasonPhrase();
785     }
786 
787     /***
788      * Defines how strictly HttpClient follows the HTTP protocol specification  
789      * (RFC 2616 and other relevant RFCs). In the strict mode HttpClient precisely
790      * implements the requirements of the specification, whereas in non-strict mode 
791      * it attempts to mimic the exact behaviour of commonly used HTTP agents, 
792      * which many HTTP servers expect.
793      * 
794      * @param strictMode <tt>true</tt> for strict mode, <tt>false</tt> otherwise
795      * 
796      * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
797      * to exercise a more granular control over HTTP protocol strictness.
798      */
799     public void setStrictMode(boolean strictMode) {
800         if (strictMode) {
801             this.params.makeStrict();
802         } else {
803             this.params.makeLenient();
804         }
805     }
806 
807     /***
808      * @deprecated Use {@link org.apache.commons.httpclient.params.HttpParams#setParameter(String, Object)}
809      * to exercise a more granular control over HTTP protocol strictness.
810      *
811      * @return <tt>false</tt>
812      */
813     public boolean isStrictMode() {
814         return false;
815     }
816 
817     /***
818      * Adds the specified request header, NOT overwriting any previous value.
819      * Note that header-name matching is case insensitive.
820      *
821      * @param headerName the header's name
822      * @param headerValue the header's value
823      */
824     public void addRequestHeader(String headerName, String headerValue) {
825         addRequestHeader(new Header(headerName, headerValue));
826     }
827 
828     /***
829      * Tests if the connection should be force-closed when no longer needed.
830      * 
831      * @return <code>true</code> if the connection must be closed
832      */
833     protected boolean isConnectionCloseForced() {
834         return this.connectionCloseForced;
835     }
836 
837     /***
838      * Sets whether or not the connection should be force-closed when no longer 
839      * needed. This value should only be set to <code>true</code> in abnormal 
840      * circumstances, such as HTTP protocol violations. 
841      * 
842      * @param b <code>true</code> if the connection must be closed, <code>false</code>
843      * otherwise.
844      */
845     protected void setConnectionCloseForced(boolean b) {
846         if (LOG.isDebugEnabled()) {
847             LOG.debug("Force-close connection: " + b);
848         }
849         this.connectionCloseForced = b;
850     }
851 
852     /***
853      * Tests if the connection should be closed after the method has been executed.
854      * The connection will be left open when using HTTP/1.1 or if <tt>Connection: 
855      * keep-alive</tt> header was sent.
856      * 
857      * @param conn the connection in question
858      * 
859      * @return boolean true if we should close the connection.
860      */
861     protected boolean shouldCloseConnection(HttpConnection conn) {
862         // Connection must be closed due to an abnormal circumstance 
863         if (isConnectionCloseForced()) {
864             LOG.debug("Should force-close connection.");
865             return true;
866         }
867 
868         Header connectionHeader = null;
869         // In case being connected via a proxy server
870         if (!conn.isTransparent()) {
871             // Check for 'proxy-connection' directive
872             connectionHeader = responseHeaders.getFirstHeader("proxy-connection");
873         }
874         // In all cases Check for 'connection' directive
875         // some non-complaint proxy servers send it instread of
876         // expected 'proxy-connection' directive
877         if (connectionHeader == null) {
878             connectionHeader = responseHeaders.getFirstHeader("connection");
879         }
880         if (connectionHeader != null) {
881             if (connectionHeader.getValue().equalsIgnoreCase("close")) {
882                 if (LOG.isDebugEnabled()) {
883                     LOG.debug("Should close connection in response to " 
884                         + connectionHeader.toExternalForm());
885                 }
886                 return true;
887             } else if (connectionHeader.getValue().equalsIgnoreCase("keep-alive")) {
888                 if (LOG.isDebugEnabled()) {
889                     LOG.debug("Should NOT close connection in response to " 
890                         + connectionHeader.toExternalForm());
891                 }
892                 return false;
893             } else {
894                 if (LOG.isDebugEnabled()) {
895                     LOG.debug("Unknown directive: " + connectionHeader.toExternalForm());
896                 }
897             }
898         }
899         LOG.debug("Resorting to protocol version default close connection policy");
900         // missing or invalid connection header, do the default
901         if (this.effectiveVersion.greaterEquals(HttpVersion.HTTP_1_1)) {
902             if (LOG.isDebugEnabled()) {
903                 LOG.debug("Should NOT close connection, using " + this.effectiveVersion.toString());
904             }
905         } else {
906             if (LOG.isDebugEnabled()) {
907                 LOG.debug("Should close connection, using " + this.effectiveVersion.toString());
908             }
909         }
910         return this.effectiveVersion.lessEquals(HttpVersion.HTTP_1_0);
911     }
912     
913     /***
914      * Tests if the this method is ready to be executed.
915      * 
916      * @param state the {@link HttpState state} information associated with this method
917      * @param conn the {@link HttpConnection connection} to be used
918      * @throws HttpException If the method is in invalid state.
919      */
920     private void checkExecuteConditions(HttpState state, HttpConnection conn)
921     throws HttpException {
922 
923         if (state == null) {
924             throw new IllegalArgumentException("HttpState parameter may not be null");
925         }
926         if (conn == null) {
927             throw new IllegalArgumentException("HttpConnection parameter may not be null");
928         }
929         if (this.aborted) {
930             throw new IllegalStateException("Method has been aborted");
931         }
932         if (!validate()) {
933             throw new ProtocolException("HttpMethodBase object not valid");
934         }
935     }
936 
937     /***
938      * Executes this method using the specified <code>HttpConnection</code> and
939      * <code>HttpState</code>. 
940      *
941      * @param state {@link HttpState state} information to associate with this
942      *        request. Must be non-null.
943      * @param conn the {@link HttpConnection connection} to used to execute
944      *        this HTTP method. Must be non-null.
945      *
946      * @return the integer status code if one was obtained, or <tt>-1</tt>
947      *
948      * @throws IOException if an I/O (transport) error occurs
949      * @throws HttpException  if a protocol exception occurs.
950      * @throws HttpRecoverableException if a recoverable transport error occurs. 
951      *                    Usually this kind of exceptions can be recovered from by
952      *                    retrying the HTTP method 
953      */
954     public int execute(HttpState state, HttpConnection conn)
955         throws HttpException, HttpRecoverableException, IOException {
956                 
957         LOG.trace("enter HttpMethodBase.execute(HttpState, HttpConnection)");
958 
959         // this is our connection now, assign it to a local variable so 
960         // that it can be released later
961         this.responseConnection = conn;
962 
963         checkExecuteConditions(state, conn);
964         this.statusLine = null;
965         this.connectionCloseForced = false;
966 
967         conn.setLastResponseInputStream(null);
968 
969         // determine the effective protocol version
970         if (this.effectiveVersion == null) {
971             this.effectiveVersion = this.params.getVersion(); 
972         }
973 
974         boolean requestSent = false;
975         writeRequest(state, conn);
976         requestSent = true;
977         readResponse(state, conn);
978         // the method has successfully executed
979         used = true; 
980 
981         return statusLine.getStatusCode();
982     }
983 
984     /***
985      * Aborts the execution of this method.
986      * 
987      * @since 3.0
988      */
989     public void abort() {
990         if (this.aborted) {
991             return;
992         }
993         this.aborted = true;
994         HttpConnection conn = this.responseConnection; 
995         if (conn != null) {
996             conn.close();
997         }
998     }
999 
1000     /***
1001      * Returns <tt>true</tt> if the HTTP method has been already {@link #execute executed},
1002      * but not {@link #recycle recycled}.
1003      * 
1004      * @return <tt>true</tt> if the method has been executed, <tt>false</tt> otherwise
1005      */
1006     public boolean hasBeenUsed() {
1007         return used;
1008     }
1009 
1010     /***
1011      * Recycles the HTTP method so that it can be used again.
1012      * Note that all of the instance variables will be reset
1013      * once this method has been called. This method will also
1014      * release the connection being used by this HTTP method.
1015      * 
1016      * @see #releaseConnection()
1017      */
1018     public void recycle() {
1019         LOG.trace("enter HttpMethodBase.recycle()");
1020 
1021         releaseConnection();
1022 
1023         path = null;
1024         followRedirects = false;
1025         doAuthentication = true;
1026         queryString = null;
1027         getRequestHeaderGroup().clear();
1028         getResponseHeaderGroup().clear();
1029         getResponseTrailerHeaderGroup().clear();
1030         statusLine = null;
1031         effectiveVersion = null;
1032         aborted = false;
1033         used = false;
1034         params = new HttpMethodParams();
1035         responseBody = null;
1036         recoverableExceptionCount = 0;
1037         connectionCloseForced = false;
1038         hostAuthState.invalidate();
1039         proxyAuthState.invalidate();
1040     }
1041 
1042     /***
1043      * Releases the connection being used by this HTTP method. In particular the
1044      * connection is used to read the response(if there is one) and will be held
1045      * until the response has been read. If the connection can be reused by other 
1046      * HTTP methods it is NOT closed at this point.
1047      *
1048      * @since 2.0
1049      */
1050     public void releaseConnection() {
1051 
1052         if (responseStream != null) {
1053             try {
1054                 // FYI - this may indirectly invoke responseBodyConsumed.
1055                 responseStream.close();
1056             } catch (IOException e) {
1057                 // the connection may not have been released, let's make sure
1058                 ensureConnectionRelease();
1059             }
1060         } else {
1061             // Make sure the connection has been released. If the response 
1062             // stream has not been set, this is the only way to release the 
1063             // connection. 
1064             ensureConnectionRelease();
1065         }
1066     }
1067 
1068     /***
1069      * Remove the request header associated with the given name. Note that
1070      * header-name matching is case insensitive.
1071      *
1072      * @param headerName the header name
1073      */
1074     public void removeRequestHeader(String headerName) {
1075         
1076         Header[] headers = getRequestHeaderGroup().getHeaders(headerName);
1077         for (int i = 0; i < headers.length; i++) {
1078             getRequestHeaderGroup().removeHeader(headers[i]);
1079         }
1080         
1081     }
1082     
1083     /***
1084      * Removes the given request header.
1085      * 
1086      * @param header the header
1087      */
1088     public void removeRequestHeader(final Header header) {
1089         if (header == null) {
1090             return;
1091         }
1092         getRequestHeaderGroup().removeHeader(header);
1093     }
1094 
1095     // ---------------------------------------------------------------- Queries
1096 
1097     /***
1098      * Returns <tt>true</tt> the method is ready to execute, <tt>false</tt> otherwise.
1099      * 
1100      * @return This implementation always returns <tt>true</tt>.
1101      */
1102     public boolean validate() {
1103         return true;
1104     }
1105 
1106 
1107 
1108     /***
1109      * Generates <tt>Cookie</tt> request headers for those {@link Cookie cookie}s
1110      * that match the given host, port and path.
1111      *
1112      * @param state the {@link HttpState state} information associated with this method
1113      * @param conn the {@link HttpConnection connection} used to execute
1114      *        this HTTP method
1115      *
1116      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1117      *                     can be recovered from.
1118      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1119      *                    cannot be recovered from.
1120      */
1121     protected void addCookieRequestHeader(HttpState state, HttpConnection conn)
1122         throws IOException, HttpException {
1123 
1124         LOG.trace("enter HttpMethodBase.addCookieRequestHeader(HttpState, "
1125                   + "HttpConnection)");
1126 
1127         Header[] cookieheaders = getRequestHeaderGroup().getHeaders("Cookie");
1128         for (int i = 0; i < cookieheaders.length; i++) {
1129             Header cookieheader = cookieheaders[i];
1130             if (cookieheader.isAutogenerated()) {
1131                 getRequestHeaderGroup().removeHeader(cookieheader);
1132             }
1133         }
1134 
1135         CookieSpec matcher = CookiePolicy.getCookieSpec(this.params.getCookiePolicy());
1136         Cookie[] cookies = matcher.match(conn.getHost(), conn.getPort(),
1137             getPath(), conn.isSecure(), state.getCookies());
1138         if ((cookies != null) && (cookies.length > 0)) {
1139             if (getParams().isParameterTrue(HttpMethodParams.SINGLE_COOKIE_HEADER)) {
1140                 // In strict mode put all cookies on the same header
1141                 Header header = matcher.formatCookieHeader(cookies);
1142                 header.setAutogenerated(true);                 
1143                 getRequestHeaderGroup().addHeader(header);
1144             } else {
1145                 // In non-strict mode put each cookie on a separate header
1146                 for (int i = 0; i < cookies.length; i++) {
1147                     Header header = matcher.formatCookieHeader(cookies[i]);
1148                     header.setAutogenerated(true);                 
1149                     getRequestHeaderGroup().addHeader(header);
1150                 }
1151             }
1152         }
1153     }
1154 
1155     /***
1156      * Generates <tt>Host</tt> request header, as long as no <tt>Host</tt> request
1157      * header already exists.
1158      *
1159      * @param state the {@link HttpState state} information associated with this method
1160      * @param conn the {@link HttpConnection connection} used to execute
1161      *        this HTTP method
1162      *
1163      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1164      *                     can be recovered from.
1165      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1166      *                    cannot be recovered from.
1167      */
1168     protected void addHostRequestHeader(HttpState state, HttpConnection conn)
1169     throws IOException, HttpException {
1170         LOG.trace("enter HttpMethodBase.addHostRequestHeader(HttpState, "
1171                   + "HttpConnection)");
1172 
1173         // Per 19.6.1.1 of RFC 2616, it is legal for HTTP/1.0 based
1174         // applications to send the Host request-header.
1175         // TODO: Add the ability to disable the sending of this header for
1176         //       HTTP/1.0 requests.
1177         String host = conn.getVirtualHost();
1178         if (host != null) {
1179             LOG.debug("Using virtual host name: " + host);
1180         } else {
1181             host = conn.getHost();
1182         }
1183         int port = conn.getPort();
1184 
1185         // Note: RFC 2616 uses the term "internet host name" for what goes on the
1186         // host line.  It would seem to imply that host should be blank if the
1187         // host is a number instead of an name.  Based on the behavior of web
1188         // browsers, and the fact that RFC 2616 never defines the phrase "internet
1189         // host name", and the bad behavior of HttpClient that follows if we
1190         // send blank, I interpret this as a small misstatement in the RFC, where
1191         // they meant to say "internet host".  So IP numbers get sent as host
1192         // entries too. -- Eric Johnson 12/13/2002
1193         if (LOG.isDebugEnabled()) {
1194             LOG.debug("Adding Host request header");
1195         }
1196 
1197         //appends the port only if not using the default port for the protocol
1198         if (conn.getProtocol().getDefaultPort() != port) {
1199             host += (":" + port);
1200         }
1201 
1202         setRequestHeader("Host", host);
1203     }
1204 
1205     /***
1206      * Generates <tt>Proxy-Connection: Keep-Alive</tt> request header when 
1207      * communicating via a proxy server.
1208      *
1209      * @param state the {@link HttpState state} information associated with this method
1210      * @param conn the {@link HttpConnection connection} used to execute
1211      *        this HTTP method
1212      *
1213      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1214      *                     can be recovered from.
1215      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1216      *                    cannot be recovered from.
1217      */
1218     protected void addProxyConnectionHeader(HttpState state,
1219                                             HttpConnection conn)
1220     throws IOException, HttpException {
1221         LOG.trace("enter HttpMethodBase.addProxyConnectionHeader("
1222                   + "HttpState, HttpConnection)");
1223         if (!conn.isTransparent()) {
1224             setRequestHeader("Proxy-Connection", "Keep-Alive");
1225         }
1226     }
1227 
1228     /***
1229      * Generates all the required request {@link Header header}s 
1230      * to be submitted via the given {@link HttpConnection connection}.
1231      *
1232      * <p>
1233      * This implementation adds <tt>User-Agent</tt>, <tt>Host</tt>,
1234      * <tt>Cookie</tt>, <tt>Authorization</tt>, <tt>Proxy-Authorization</tt>
1235      * and <tt>Proxy-Connection</tt> headers, when appropriate.
1236      * </p>
1237      *
1238      * <p>
1239      * Subclasses may want to override this method to to add additional
1240      * headers, and may choose to invoke this implementation (via
1241      * <tt>super</tt>) to add the "standard" headers.
1242      * </p>
1243      *
1244      * @param state the {@link HttpState state} information associated with this method
1245      * @param conn the {@link HttpConnection connection} used to execute
1246      *        this HTTP method
1247      *
1248      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1249      *                     can be recovered from.
1250      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1251      *                    cannot be recovered from.
1252      *
1253      * @see #writeRequestHeaders
1254      */
1255     protected void addRequestHeaders(HttpState state, HttpConnection conn)
1256     throws IOException, HttpException {
1257         LOG.trace("enter HttpMethodBase.addRequestHeaders(HttpState, "
1258             + "HttpConnection)");
1259 
1260         addUserAgentRequestHeader(state, conn);
1261         addHostRequestHeader(state, conn);
1262         addCookieRequestHeader(state, conn);
1263         addProxyConnectionHeader(state, conn);
1264     }
1265 
1266     /***
1267      * Generates default <tt>User-Agent</tt> request header, as long as no
1268      * <tt>User-Agent</tt> request header already exists.
1269      *
1270      * @param state the {@link HttpState state} information associated with this method
1271      * @param conn the {@link HttpConnection connection} used to execute
1272      *        this HTTP method
1273      *
1274      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1275      *                     can be recovered from.
1276      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1277      *                    cannot be recovered from.
1278      */
1279     protected void addUserAgentRequestHeader(HttpState state,
1280                                              HttpConnection conn)
1281     throws IOException, HttpException {
1282         LOG.trace("enter HttpMethodBase.addUserAgentRequestHeaders(HttpState, "
1283             + "HttpConnection)");
1284 
1285         if (getRequestHeader("User-Agent") == null) {
1286             String agent = (String)getParams().getParameter(HttpMethodParams.USER_AGENT);
1287             if (agent == null) {
1288                 agent = "Jakarta Commons-HttpClient";
1289             }
1290             setRequestHeader("User-Agent", agent);
1291         }
1292     }
1293 
1294     /***
1295      * Throws an {@link IllegalStateException} if the HTTP method has been already
1296      * {@link #execute executed}, but not {@link #recycle recycled}.
1297      *
1298      * @throws IllegalStateException if the method has been used and not
1299      *      recycled
1300      */
1301     protected void checkNotUsed() throws IllegalStateException {
1302         if (used) {
1303             throw new IllegalStateException("Already used.");
1304         }
1305     }
1306 
1307     /***
1308      * Throws an {@link IllegalStateException} if the HTTP method has not been
1309      * {@link #execute executed} since last {@link #recycle recycle}.
1310      *
1311      *
1312      * @throws IllegalStateException if not used
1313      */
1314     protected void checkUsed()  throws IllegalStateException {
1315         if (!used) {
1316             throw new IllegalStateException("Not Used.");
1317         }
1318     }
1319 
1320     // ------------------------------------------------- Static Utility Methods
1321 
1322     /***
1323      * Generates HTTP request line according to the specified attributes.
1324      *
1325      * @param connection the {@link HttpConnection connection} used to execute
1326      *        this HTTP method
1327      * @param name the method name generate a request for
1328      * @param requestPath the path string for the request
1329      * @param query the query string for the request
1330      * @param version the protocol version to use (e.g. HTTP/1.0)
1331      *
1332      * @return HTTP request line
1333      */
1334     protected static String generateRequestLine(HttpConnection connection,
1335         String name, String requestPath, String query, String version) {
1336         LOG.trace("enter HttpMethodBase.generateRequestLine(HttpConnection, "
1337             + "String, String, String, String)");
1338 
1339         StringBuffer buf = new StringBuffer();
1340         // Append method name
1341         buf.append(name);
1342         buf.append(" ");
1343         // Absolute or relative URL?
1344         if (!connection.isTransparent()) {
1345             Protocol protocol = connection.getProtocol();
1346             buf.append(protocol.getScheme().toLowerCase());
1347             buf.append("://");
1348             buf.append(connection.getHost());
1349             if ((connection.getPort() != -1) 
1350                 && (connection.getPort() != protocol.getDefaultPort())
1351             ) {
1352                 buf.append(":");
1353                 buf.append(connection.getPort());
1354             }
1355         }
1356         // Append path, if any
1357         if (requestPath == null) {
1358             buf.append("/");
1359         } else {
1360             if (!connection.isTransparent() && !requestPath.startsWith("/")) {
1361                 buf.append("/");
1362             }
1363             buf.append(requestPath);
1364         }
1365         // Append query, if any
1366         if (query != null) {
1367             if (query.indexOf("?") != 0) {
1368                 buf.append("?");
1369             }
1370             buf.append(query);
1371         }
1372         // Append protocol
1373         buf.append(" ");
1374         buf.append(version);
1375         buf.append("\r\n");
1376         
1377         return buf.toString();
1378     }
1379     
1380     /***
1381      * This method is invoked immediately after 
1382      * {@link #readResponseBody(HttpState,HttpConnection)} and can be overridden by
1383      * sub-classes in order to provide custom body processing.
1384      *
1385      * <p>
1386      * This implementation does nothing.
1387      * </p>
1388      *
1389      * @param state the {@link HttpState state} information associated with this method
1390      * @param conn the {@link HttpConnection connection} used to execute
1391      *        this HTTP method
1392      *
1393      * @see #readResponse
1394      * @see #readResponseBody
1395      */
1396     protected void processResponseBody(HttpState state, HttpConnection conn) {
1397     }
1398 
1399     /***
1400      * This method is invoked immediately after 
1401      * {@link #readResponseHeaders(HttpState,HttpConnection)} and can be overridden by
1402      * sub-classes in order to provide custom response headers processing.
1403 
1404      * <p>
1405      * This implementation will handle the <tt>Set-Cookie</tt> and
1406      * <tt>Set-Cookie2</tt> headers, if any, adding the relevant cookies to
1407      * the given {@link HttpState}.
1408      * </p>
1409      *
1410      * @param state the {@link HttpState state} information associated with this method
1411      * @param conn the {@link HttpConnection connection} used to execute
1412      *        this HTTP method
1413      *
1414      * @see #readResponse
1415      * @see #readResponseHeaders
1416      */
1417     protected void processResponseHeaders(HttpState state,
1418         HttpConnection conn) {
1419         LOG.trace("enter HttpMethodBase.processResponseHeaders(HttpState, "
1420             + "HttpConnection)");
1421 
1422         Header[] headers = getResponseHeaderGroup().getHeaders("set-cookie2");
1423         //Only process old style set-cookie headers if new style headres
1424         //are not present
1425         if (headers.length == 0) { 
1426             headers = getResponseHeaderGroup().getHeaders("set-cookie");
1427         }
1428         
1429         CookieSpec parser = CookiePolicy.getCookieSpec(this.params.getCookiePolicy());
1430         for (int i = 0; i < headers.length; i++) {
1431             Header header = headers[i];
1432             Cookie[] cookies = null;
1433             try {
1434                 cookies = parser.parse(
1435                   conn.getHost(),
1436                   conn.getPort(),
1437                   getPath(),
1438                   conn.isSecure(),
1439                   header);
1440             } catch (MalformedCookieException e) {
1441                 if (LOG.isWarnEnabled()) {
1442                     LOG.warn("Invalid cookie header: \"" 
1443                         + header.getValue() 
1444                         + "\". " + e.getMessage());
1445                 }
1446             }
1447             if (cookies != null) {
1448                 for (int j = 0; j < cookies.length; j++) {
1449                     Cookie cookie = cookies[j];
1450                     try {
1451                         parser.validate(
1452                           conn.getHost(),
1453                           conn.getPort(),
1454                           getPath(),
1455                           conn.isSecure(),
1456                           cookie);
1457                         state.addCookie(cookie);
1458                         if (LOG.isDebugEnabled()) {
1459                             LOG.debug("Cookie accepted: \"" 
1460                                 + parser.formatCookie(cookie) + "\"");
1461                         }
1462                     } catch (MalformedCookieException e) {
1463                         if (LOG.isWarnEnabled()) {
1464                             LOG.warn("Cookie rejected: \"" + parser.formatCookie(cookie) 
1465                                 + "\". " + e.getMessage());
1466                         }
1467                     }
1468                 }
1469             }
1470         }
1471     }
1472 
1473     /***
1474      * This method is invoked immediately after 
1475      * {@link #readStatusLine(HttpState,HttpConnection)} and can be overridden by
1476      * sub-classes in order to provide custom response status line processing.
1477      *
1478      * @param state the {@link HttpState state} information associated with this method
1479      * @param conn the {@link HttpConnection connection} used to execute
1480      *        this HTTP method
1481      *
1482      * @see #readResponse
1483      * @see #readStatusLine
1484      */
1485     protected void processStatusLine(HttpState state, HttpConnection conn) {
1486     }
1487 
1488     /***
1489      * Reads the response from the given {@link HttpConnection connection}.
1490      *
1491      * <p>
1492      * The response is processed as the following sequence of actions:
1493      *
1494      * <ol>
1495      * <li>
1496      * {@link #readStatusLine(HttpState,HttpConnection)} is
1497      * invoked to read the request line.
1498      * </li>
1499      * <li>
1500      * {@link #processStatusLine(HttpState,HttpConnection)}
1501      * is invoked, allowing the method to process the status line if
1502      * desired.
1503      * </li>
1504      * <li>
1505      * {@link #readResponseHeaders(HttpState,HttpConnection)} is invoked to read
1506      * the associated headers.
1507      * </li>
1508      * <li>
1509      * {@link #processResponseHeaders(HttpState,HttpConnection)} is invoked, allowing
1510      * the method to process the headers if desired.
1511      * </li>
1512      * <li>
1513      * {@link #readResponseBody(HttpState,HttpConnection)} is
1514      * invoked to read the associated body (if any).
1515      * </li>
1516      * <li>
1517      * {@link #processResponseBody(HttpState,HttpConnection)} is invoked, allowing the
1518      * method to process the response body if desired.
1519      * </li>
1520      * </ol>
1521      *
1522      * Subclasses may want to override one or more of the above methods to to
1523      * customize the processing. (Or they may choose to override this method
1524      * if dramatically different processing is required.)
1525      * </p>
1526      *
1527      * @param state the {@link HttpState state} information associated with this method
1528      * @param conn the {@link HttpConnection connection} used to execute
1529      *        this HTTP method
1530      *
1531      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1532      *                     can be recovered from.
1533      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1534      *                    cannot be recovered from.
1535      */
1536     protected void readResponse(HttpState state, HttpConnection conn)
1537     throws IOException, HttpException {
1538         LOG.trace(
1539         "enter HttpMethodBase.readResponse(HttpState, HttpConnection)");
1540         // Status line & line may have already been received
1541         // if 'expect - continue' handshake has been used
1542         while (this.statusLine == null) {
1543             readStatusLine(state, conn);
1544             processStatusLine(state, conn);
1545             readResponseHeaders(state, conn);
1546             processResponseHeaders(state, conn);
1547             
1548             int status = this.statusLine.getStatusCode();
1549             if ((status >= 100) && (status < 200)) {
1550                 if (LOG.isInfoEnabled()) {
1551                     LOG.info("Discarding unexpected response: " + this.statusLine.toString()); 
1552                 }
1553                 this.statusLine = null;
1554             }
1555         }
1556         readResponseBody(state, conn);
1557         processResponseBody(state, conn);
1558     }
1559 
1560     /***
1561      * Read the response body from the given {@link HttpConnection}.
1562      *
1563      * <p>
1564      * The current implementation wraps the socket level stream with
1565      * an appropriate stream for the type of response (chunked, content-length,
1566      * or auto-close).  If there is no response body, the connection associated
1567      * with the request will be returned to the connection manager.
1568      * </p>
1569      *
1570      * <p>
1571      * Subclasses may want to override this method to to customize the
1572      * processing.
1573      * </p>
1574      *
1575      * @param state the {@link HttpState state} information associated with this method
1576      * @param conn the {@link HttpConnection connection} used to execute
1577      *        this HTTP method
1578      *
1579      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1580      *                     can be recovered from.
1581      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1582      *                    cannot be recovered from.
1583      *
1584      * @see #readResponse
1585      * @see #processResponseBody
1586      */
1587     protected void readResponseBody(HttpState state, HttpConnection conn)
1588     throws IOException, HttpException {
1589         LOG.trace(
1590             "enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1591 
1592         // assume we are not done with the connection if we get a stream
1593         InputStream stream = readResponseBody(conn);
1594         if (stream == null) {
1595             // done using the connection!
1596             responseBodyConsumed();
1597         } else {
1598             conn.setLastResponseInputStream(stream);
1599             setResponseStream(stream);
1600         }
1601     }
1602 
1603     /***
1604      * Returns the response body as an {@link InputStream input stream}
1605      * corresponding to the values of the <tt>Content-Length</tt> and 
1606      * <tt>Transfer-Encoding</tt> headers. If no response body is available
1607      * returns <tt>null</tt>.
1608      * <p>
1609      *
1610      * @see #readResponse
1611      * @see #processResponseBody
1612      *
1613      * @param conn the {@link HttpConnection connection} used to execute
1614      *        this HTTP method
1615      *
1616      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1617      *                     can be recovered from.
1618      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1619      *                    cannot be recovered from.
1620      */
1621     private InputStream readResponseBody(HttpConnection conn)
1622         throws HttpException, IOException {
1623 
1624         LOG.trace("enter HttpMethodBase.readResponseBody(HttpState, HttpConnection)");
1625 
1626         responseBody = null; // is this desired?
1627         InputStream is = conn.getResponseInputStream();
1628         if (Wire.traceEnabled()) {
1629             is = new WireLogInputStream(is);
1630         }
1631         InputStream result = null;
1632         Header transferEncodingHeader = responseHeaders.getFirstHeader("Transfer-Encoding");
1633         // We use Transfer-Encoding if present and ignore Content-Length.
1634         // RFC2616, 4.4 item number 3
1635         if (transferEncodingHeader != null) {
1636 
1637             String transferEncoding = transferEncodingHeader.getValue();
1638             if (!"chunked".equalsIgnoreCase(transferEncoding) 
1639                 && !"identity".equalsIgnoreCase(transferEncoding)) {
1640                 if (LOG.isWarnEnabled()) {
1641                     LOG.warn("Unsupported transfer encoding: " + transferEncoding);
1642                 }
1643             }
1644             HeaderElement[] encodings = transferEncodingHeader.getElements();
1645             // The chunked encoding must be the last one applied
1646             // RFC2616, 14.41
1647             int len = encodings.length;            
1648             if ((len > 0) && ("chunked".equalsIgnoreCase(encodings[len - 1].getName()))) { 
1649                 // if response body is empty
1650                 if (conn.isResponseAvailable(conn.getParams().getSoTimeout())) {
1651                     result = new ChunkedInputStream(is, this);
1652                 } else {
1653                     if (getParams().isParameterTrue(HttpMethodParams.STRICT_TRANSFER_ENCODING)) {
1654                         throw new ProtocolException("Chunk-encoded body declared but not sent");
1655                     } else {
1656                         LOG.warn("Chunk-encoded body missing");
1657                     }
1658                 }
1659             } else {
1660                 if (LOG.isWarnEnabled()) {
1661                     LOG.warn("Transfer-Encoding is set but does not contain \"chunked\": "
1662                         + transferEncoding);
1663                 }
1664                 // The connection must be terminated by closing 
1665                 // the socket as per RFC 2616, 3.6
1666                 setConnectionCloseForced(true);
1667                 result = is;  
1668             }
1669         } else {
1670             long expectedLength = getResponseContentLength();
1671             if (expectedLength == -1) {
1672                 if (canResponseHaveBody(statusLine.getStatusCode())) {
1673                     Header connectionHeader = responseHeaders.getFirstHeader("Connection");
1674                     String connectionDirective = null;
1675                     if (connectionHeader != null) {
1676                         connectionDirective = connectionHeader.getValue();
1677                     }
1678                     if (!"close".equalsIgnoreCase(connectionDirective)) {
1679                         LOG.warn("Response content length is not known");
1680                         setConnectionCloseForced(true);
1681                     }
1682                     result = is;            
1683                 }
1684             } else {
1685                 result = new ContentLengthInputStream(is, expectedLength);
1686             }
1687         } 
1688         // if there is a result - ALWAYS wrap it in an observer which will
1689         // close the underlying stream as soon as it is consumed, and notify
1690         // the watcher that the stream has been consumed.
1691         if (result != null) {
1692 
1693             result = new AutoCloseInputStream(
1694                 result,
1695                 new ResponseConsumedWatcher() {
1696                     public void responseConsumed() {
1697                         responseBodyConsumed();
1698                     }
1699                 }
1700             );
1701         }
1702 
1703         return result;
1704     }
1705 
1706     /***
1707      * Reads the response headers from the given {@link HttpConnection connection}.
1708      *
1709      * <p>
1710      * Subclasses may want to override this method to to customize the
1711      * processing.
1712      * </p>
1713      *
1714      * <p>
1715      * "It must be possible to combine the multiple header fields into one
1716      * "field-name: field-value" pair, without changing the semantics of the
1717      * message, by appending each subsequent field-value to the first, each
1718      * separated by a comma." - HTTP/1.0 (4.3)
1719      * </p>
1720      *
1721      * @param state the {@link HttpState state} information associated with this method
1722      * @param conn the {@link HttpConnection connection} used to execute
1723      *        this HTTP method
1724      *
1725      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1726      *                     can be recovered from.
1727      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1728      *                    cannot be recovered from.
1729      *
1730      * @see #readResponse
1731      * @see #processResponseHeaders
1732      */
1733     protected void readResponseHeaders(HttpState state, HttpConnection conn)
1734     throws IOException, HttpException {
1735         LOG.trace("enter HttpMethodBase.readResponseHeaders(HttpState,"
1736             + "HttpConnection)");
1737 
1738         getResponseHeaderGroup().clear();
1739         
1740         Header[] headers = HttpParser.parseHeaders(
1741             conn.getResponseInputStream(), getParams().getHttpElementCharset());
1742         if (Wire.enabled()) {
1743             for (int i = 0; i < headers.length; i++) {
1744                 Wire.input(headers[i].toExternalForm());
1745             }
1746         }
1747         getResponseHeaderGroup().setHeaders(headers);
1748     }
1749 
1750     /***
1751      * Read the status line from the given {@link HttpConnection}, setting my
1752      * {@link #getStatusCode status code} and {@link #getStatusText status
1753      * text}.
1754      *
1755      * <p>
1756      * Subclasses may want to override this method to to customize the
1757      * processing.
1758      * </p>
1759      *
1760      * @param state the {@link HttpState state} information associated with this method
1761      * @param conn the {@link HttpConnection connection} used to execute
1762      *        this HTTP method
1763      *
1764      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1765      *                     can be recovered from.
1766      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1767      *                    cannot be recovered from.
1768      *
1769      * @see StatusLine
1770      */
1771     protected void readStatusLine(HttpState state, HttpConnection conn)
1772     throws IOException, HttpRecoverableException, HttpException {
1773         LOG.trace("enter HttpMethodBase.readStatusLine(HttpState, HttpConnection)");
1774 
1775         final int maxGarbageLines = getParams().
1776             getIntParameter(HttpMethodParams.STATUS_LINE_GARBAGE_LIMIT, Integer.MAX_VALUE);
1777 
1778         //read out the HTTP status string
1779         int count = 0;
1780         String s;
1781         do {
1782             s = conn.readLine(getParams().getHttpElementCharset());
1783             if (Wire.enabled()) {
1784                 Wire.input(s + "\r\n");
1785             }
1786             if (s != null && StatusLine.startsWithHTTP(s)) {
1787                 // Got one
1788                 break;
1789             } else if (s == null || count >= maxGarbageLines) {
1790                 // Giving up
1791                 throw new HttpRecoverableException("Error in parsing the status "
1792                     + " line from the response: unable to find line starting with"
1793                     + " \"HTTP\"");
1794             }
1795             count++;
1796         } while(true);
1797 
1798         //create the status line from the status string
1799         statusLine = new StatusLine(s);
1800 
1801         //check for a valid HTTP-Version
1802         String versionStr = statusLine.getHttpVersion();
1803         if (getParams().isParameterFalse(HttpMethodParams.UNAMBIGUOUS_STATUS_LINE) 
1804            && versionStr.equals("HTTP")) {
1805             getParams().setVersion(HttpVersion.HTTP_1_0);
1806             if (LOG.isWarnEnabled()) {
1807                 LOG.warn("Ambiguous status line (HTTP protocol version missing):" +
1808                 statusLine.toString());
1809             }
1810         } else {
1811             this.effectiveVersion = HttpVersion.parse(versionStr);
1812         }
1813 
1814     }
1815 
1816     // ------------------------------------------------------ Protected Methods
1817 
1818     /***
1819      * <p>
1820      * Sends the request via the given {@link HttpConnection connection}.
1821      * </p>
1822      *
1823      * <p>
1824      * The request is written as the following sequence of actions:
1825      * </p>
1826      *
1827      * <ol>
1828      * <li>
1829      * {@link #writeRequestLine(HttpState, HttpConnection)} is invoked to 
1830      * write the request line.
1831      * </li>
1832      * <li>
1833      * {@link #writeRequestHeaders(HttpState, HttpConnection)} is invoked 
1834      * to write the associated headers.
1835      * </li>
1836      * <li>
1837      * <tt>\r\n</tt> is sent to close the head part of the request.
1838      * </li>
1839      * <li>
1840      * {@link #writeRequestBody(HttpState, HttpConnection)} is invoked to 
1841      * write the body part of the request.
1842      * </li>
1843      * </ol>
1844      *
1845      * <p>
1846      * Subclasses may want to override one or more of the above methods to to
1847      * customize the processing. (Or they may choose to override this method
1848      * if dramatically different processing is required.)
1849      * </p>
1850      *
1851      * @param state the {@link HttpState state} information associated with this method
1852      * @param conn the {@link HttpConnection connection} used to execute
1853      *        this HTTP method
1854      *
1855      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1856      *                     can be recovered from.
1857      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1858      *                    cannot be recovered from.
1859      */
1860     protected void writeRequest(HttpState state, HttpConnection conn)
1861     throws IOException, HttpException {
1862         LOG.trace(
1863             "enter HttpMethodBase.writeRequest(HttpState, HttpConnection)");
1864         writeRequestLine(state, conn);
1865         writeRequestHeaders(state, conn);
1866         conn.writeLine(); // close head
1867         // make sure the status line and headers have been sent
1868         conn.flushRequestOutputStream();
1869         if (Wire.enabled()) {
1870             Wire.output("\r\n");
1871         }
1872 
1873         HttpVersion ver = getParams().getVersion();
1874         Header expectheader = getRequestHeader("Expect");
1875         String expectvalue = null;
1876         if (expectheader != null) {
1877             expectvalue = expectheader.getValue();
1878         }
1879         if ((expectvalue != null) 
1880          && (expectvalue.compareToIgnoreCase("100-continue") == 0)) {
1881             if (ver.greaterEquals(HttpVersion.HTTP_1_1)) {
1882                 int readTimeout = conn.getParams().getSoTimeout();
1883                 try {
1884                     conn.setSocketTimeout(RESPONSE_WAIT_TIME_MS);
1885                     readStatusLine(state, conn);
1886                     processStatusLine(state, conn);
1887                     readResponseHeaders(state, conn);
1888                     processResponseHeaders(state, conn);
1889 
1890                     if (this.statusLine.getStatusCode() == HttpStatus.SC_CONTINUE) {
1891                         // Discard status line
1892                         this.statusLine = null;
1893                         LOG.debug("OK to continue received");
1894                     } else {
1895                         return;
1896                     }
1897                 } catch (IOTimeoutException e) {
1898                     // Most probably Expect header is not recongnized
1899                     // Remove the header to signal the method 
1900                     // that it's okay to go ahead with sending data
1901                     removeRequestHeader("Expect");
1902                     LOG.info("100 (continue) read timeout. Resume sending the request");
1903                 } finally {
1904                     conn.setSocketTimeout(readTimeout);
1905                 }
1906                 
1907             } else {
1908                 removeRequestHeader("Expect");
1909                 LOG.info("'Expect: 100-continue' handshake is only supported by "
1910                     + "HTTP/1.1 or higher");
1911             }
1912         }
1913 
1914         writeRequestBody(state, conn);
1915         // make sure the entire request body has been sent
1916         conn.flushRequestOutputStream();
1917     }
1918 
1919     /***
1920      * Writes the request body to the given {@link HttpConnection connection}.
1921      *
1922      * <p>
1923      * This method should return <tt>true</tt> if the request body was actually
1924      * sent (or is empty), or <tt>false</tt> if it could not be sent for some
1925      * reason.
1926      * </p>
1927      *
1928      * <p>
1929      * This implementation writes nothing and returns <tt>true</tt>.
1930      * </p>
1931      *
1932      * @param state the {@link HttpState state} information associated with this method
1933      * @param conn the {@link HttpConnection connection} used to execute
1934      *        this HTTP method
1935      *
1936      * @return <tt>true</tt>
1937      *
1938      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1939      *                     can be recovered from.
1940      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1941      *                    cannot be recovered from.
1942      */
1943     protected boolean writeRequestBody(HttpState state, HttpConnection conn)
1944     throws IOException, HttpException {
1945         return true;
1946     }
1947 
1948     /***
1949      * Writes the request headers to the given {@link HttpConnection connection}.
1950      *
1951      * <p>
1952      * This implementation invokes {@link #addRequestHeaders(HttpState,HttpConnection)},
1953      * and then writes each header to the request stream.
1954      * </p>
1955      *
1956      * <p>
1957      * Subclasses may want to override this method to to customize the
1958      * processing.
1959      * </p>
1960      *
1961      * @param state the {@link HttpState state} information associated with this method
1962      * @param conn the {@link HttpConnection connection} used to execute
1963      *        this HTTP method
1964      *
1965      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
1966      *                     can be recovered from.
1967      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
1968      *                    cannot be recovered from.
1969      *
1970      * @see #addRequestHeaders
1971      * @see #getRequestHeaders
1972      */
1973     protected void writeRequestHeaders(HttpState state, HttpConnection conn)
1974     throws IOException, HttpException {
1975         LOG.trace("enter HttpMethodBase.writeRequestHeaders(HttpState,"
1976             + "HttpConnection)");
1977         addRequestHeaders(state, conn);
1978 
1979         String charset = getParams().getHttpElementCharset();
1980         
1981         Header[] headers = getRequestHeaders();
1982         for (int i = 0; i < headers.length; i++) {
1983             String s = headers[i].toExternalForm();
1984             if (Wire.enabled()) {
1985                 Wire.output(s);
1986             }
1987             conn.print(s, charset);
1988         }
1989     }
1990 
1991     /***
1992      * Writes the request line to the given {@link HttpConnection connection}.
1993      *
1994      * <p>
1995      * Subclasses may want to override this method to to customize the
1996      * processing.
1997      * </p>
1998      *
1999      * @param state the {@link HttpState state} information associated with this method
2000      * @param conn the {@link HttpConnection connection} used to execute
2001      *        this HTTP method
2002      *
2003      * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
2004      *                     can be recovered from.
2005      * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
2006      *                    cannot be recovered from.
2007      *
2008      * @see #generateRequestLine
2009      */
2010     protected void writeRequestLine(HttpState state, HttpConnection conn)
2011     throws IOException, HttpException {
2012         LOG.trace(
2013             "enter HttpMethodBase.writeRequestLine(HttpState, HttpConnection)");
2014         String requestLine = getRequestLine(conn);
2015         if (Wire.enabled()) {
2016             Wire.output(requestLine);
2017         }
2018         conn.print(requestLine, getParams().getHttpElementCharset());
2019     }
2020 
2021     /***
2022      * Returns the request line.
2023      * 
2024      * @param conn the {@link HttpConnection connection} used to execute
2025      *        this HTTP method
2026      * 
2027      * @return The request line.
2028      */
2029     private String getRequestLine(HttpConnection conn) {
2030         return  HttpMethodBase.generateRequestLine(conn, getName(),
2031                 getPath(), getQueryString(), this.effectiveVersion.toString());
2032     }
2033 
2034     /***
2035      * Returns {@link HttpMethodParams HTTP protocol parameters} associated with this method.
2036      *
2037      * @return HTTP parameters.
2038      *
2039      * @since 3.0
2040      */
2041     public HttpMethodParams getParams() {
2042         return this.params;
2043     }
2044 
2045     /***
2046      * Assigns {@link HttpMethodParams HTTP protocol parameters} for this method.
2047      * 
2048      * @since 3.0
2049      * 
2050      * @see HttpMethodParams
2051      */
2052     public void setParams(final HttpMethodParams params) {
2053         if (params == null) {
2054             throw new IllegalArgumentException("Parameters may not be null");
2055         }
2056         this.params = params;
2057     }
2058 
2059     /***
2060      * Returns the HTTP version used with this method (may be <tt>null</tt>
2061      * if undefined, that is, the method has not been executed)
2062      *
2063      * @return HTTP version.
2064      *
2065      * @since 3.0
2066      */
2067     public HttpVersion getEffectiveVersion() {
2068         return this.effectiveVersion;
2069     }
2070 
2071     /***
2072      * Per RFC 2616 section 4.3, some response can never contain a message
2073      * body.
2074      *
2075      * @param status - the HTTP status code
2076      *
2077      * @return <tt>true</tt> if the message may contain a body, <tt>false</tt> if it can not
2078      *         contain a message body
2079      */
2080     private static boolean canResponseHaveBody(int status) {
2081         LOG.trace("enter HttpMethodBase.canResponseHaveBody(int)");
2082 
2083         boolean result = true;
2084 
2085         if ((status >= 100 && status <= 199) || (status == 204)
2086             || (status == 304)) { // NOT MODIFIED
2087             result = false;
2088         }
2089 
2090         return result;
2091     }
2092 
2093     /***
2094      * Returns proxy authentication realm, if it has been used during authentication process. 
2095      * Otherwise returns <tt>null</tt>.
2096      * 
2097      * @return proxy authentication realm
2098      * 
2099      * @deprecated use #getProxyAuthState()
2100      */
2101     public String getProxyAuthenticationRealm() {
2102         return this.proxyAuthState.getRealm();
2103     }
2104 
2105     /***
2106      * Returns authentication realm, if it has been used during authentication process. 
2107      * Otherwise returns <tt>null</tt>.
2108      * 
2109      * @return authentication realm
2110      * 
2111      * @deprecated use #getHostAuthState()
2112      */
2113     public String getAuthenticationRealm() {
2114         return this.hostAuthState.getRealm();
2115     }
2116 
2117     /***
2118      * Returns the character set from the <tt>Content-Type</tt> header.
2119      * 
2120      * @param contentheader The content header.
2121      * @return String The character set.
2122      */
2123     protected String getContentCharSet(Header contentheader) {
2124         LOG.trace("enter getContentCharSet( Header contentheader )");
2125         String charset = null;
2126         if (contentheader != null) {
2127             HeaderElement values[] = contentheader.getElements();
2128             // I expect only one header element to be there
2129             // No more. no less
2130             if (values.length == 1) {
2131                 NameValuePair param = values[0].getParameterByName("charset");
2132                 if (param != null) {
2133                     // If I get anything "funny" 
2134                     // UnsupportedEncondingException will result
2135                     charset = param.getValue();
2136                 }
2137             }
2138         }
2139         if (charset == null) {
2140             charset = getParams().getContentCharset();
2141             if (LOG.isDebugEnabled()) {
2142                 LOG.debug("Default charset used: " + charset);
2143             }
2144         }
2145         return charset;
2146     }
2147 
2148 
2149     /***
2150      * Returns the character encoding of the request from the <tt>Content-Type</tt> header.
2151      * 
2152      * @return String The character set.
2153      */
2154     public String getRequestCharSet() {
2155         return getContentCharSet(getRequestHeader("Content-Type"));
2156     }
2157 
2158 
2159     /***  
2160      * Returns the character encoding of the response from the <tt>Content-Type</tt> header.
2161      * 
2162      * @return String The character set.
2163      */
2164     public String getResponseCharSet() {
2165         return getContentCharSet(getResponseHeader("Content-Type"));
2166     }
2167 
2168     /***
2169      * @deprecated no longer used
2170      * 
2171      * Returns the number of "recoverable" exceptions thrown and handled, to
2172      * allow for monitoring the quality of the connection.
2173      *
2174      * @return The number of recoverable exceptions handled by the method.
2175      */
2176     public int getRecoverableExceptionCount() {
2177         return recoverableExceptionCount;
2178     }
2179 
2180     /***
2181      * A response has been consumed.
2182      *
2183      * <p>The default behavior for this class is to check to see if the connection
2184      * should be closed, and close if need be, and to ensure that the connection
2185      * is returned to the connection manager - if and only if we are not still
2186      * inside the execute call.</p>
2187      *
2188      */
2189     protected void responseBodyConsumed() {
2190 
2191         // make sure this is the initial invocation of the notification,
2192         // ignore subsequent ones.
2193         responseStream = null;
2194         if (responseConnection != null) {
2195             responseConnection.setLastResponseInputStream(null);
2196 
2197             // At this point, no response data should be available.
2198             // If there is data available, regard the connection as being
2199             // unreliable and close it.
2200             
2201             try {
2202                 if(responseConnection.isResponseAvailable()) {
2203                     boolean logExtraInput =
2204                         getParams().isParameterTrue(HttpMethodParams.WARN_EXTRA_INPUT);
2205 
2206                     if(logExtraInput) {
2207                         LOG.warn("Extra response data detected - closing connection");
2208                     } 
2209                     setConnectionCloseForced(true);
2210                 }
2211             }
2212             catch (IOException e) {
2213                 LOG.info(e.getMessage());
2214                 responseConnection.close();
2215             }
2216             if (shouldCloseConnection(responseConnection)) {
2217                 responseConnection.close();
2218             }
2219         }
2220         this.connectionCloseForced = false;
2221         ensureConnectionRelease();
2222     }
2223 
2224     /***
2225      * Insure that the connection is released back to the pool.
2226      */
2227     private void ensureConnectionRelease() {
2228         if (responseConnection != null) {
2229             responseConnection.releaseConnection();
2230             responseConnection = null;
2231         }
2232     }
2233 
2234     /***
2235      * Returns the {@link HostConfiguration host configuration}.
2236      * 
2237      * @return the host configuration
2238      */
2239     public HostConfiguration getHostConfiguration() {
2240         return hostConfiguration;
2241     }
2242 
2243     /***
2244      * Sets the {@link HostConfiguration host configuration}.
2245      * 
2246      * @param hostConfiguration The hostConfiguration to set
2247      */
2248     public void setHostConfiguration(HostConfiguration hostConfiguration) {
2249         this.hostConfiguration = hostConfiguration;
2250     }
2251 
2252     /***
2253      * Returns the {@link MethodRetryHandler retry handler} for this HTTP method
2254      * 
2255      * @return the methodRetryHandler
2256      */
2257     public MethodRetryHandler getMethodRetryHandler() {
2258         
2259         if (methodRetryHandler == null) {
2260             methodRetryHandler = new DefaultMethodRetryHandler();
2261         }
2262 
2263         return methodRetryHandler;
2264     }
2265 
2266     /***
2267      * Sets the {@link MethodRetryHandler retry handler} for this HTTP method
2268      * 
2269      * @param handler the methodRetryHandler to use when this method executed
2270      */
2271     public void setMethodRetryHandler(MethodRetryHandler handler) {
2272         methodRetryHandler = handler;
2273     }
2274 
2275     /***
2276      * This method is a dirty hack intended to work around 
2277      * current (2.0) design flaw that prevents the user from
2278      * obtaining correct status code, headers and response body from the 
2279      * preceding HTTP CONNECT method.
2280      * 
2281      * TODO: Remove this crap as soon as possible
2282      */
2283     void fakeResponse(
2284         StatusLine statusline, 
2285         HeaderGroup responseheaders,
2286         InputStream responseStream
2287     ) {
2288         // set used so that the response can be read
2289         this.used = true;
2290         this.statusLine = statusline;
2291         this.responseHeaders = responseheaders;
2292         this.responseBody = null;
2293         this.responseStream = responseStream;
2294     }
2295     
2296     /***
2297      * Returns the target host {@link AuthState authentication state}
2298      * 
2299      * @return host authentication state
2300      * 
2301      * @since 3.0
2302      */
2303     public AuthState getHostAuthState() {
2304         return this.hostAuthState;
2305     }
2306 
2307     /***
2308      * Returns the proxy {@link AuthState authentication state}
2309      * 
2310      * @return host authentication state
2311      * 
2312      * @since 3.0
2313      */
2314     public AuthState getProxyAuthState() {
2315         return this.proxyAuthState;
2316     }
2317     
2318     /***
2319      * Tests whether the execution of this method has been aborted
2320      * 
2321      * @return <tt>true</tt> if the execution of this method has been aborted,
2322      *  <tt>false</tt> otherwise
2323      * 
2324      * @since 3.0
2325      */
2326     public boolean isAborted() {
2327         return this.aborted;
2328     }    
2329 }