View Javadoc

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