HttpMethodBase xref

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