Home » commons-httpclient-3.1-src » org.apache.commons » httpclient » [javadoc | source]
    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   }

Save This Page
Home » commons-httpclient-3.1-src » org.apache.commons » httpclient » [javadoc | source]