Home » commons-httpclient-3.1-src » org.apache.commons » httpclient » methods » [javadoc | source]
    1   /*
    2    * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.39 2004/07/03 14:27:03 olegk Exp $
    3    * $Revision: 480424 $
    4    * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
    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.methods;
   32   
   33   import java.io.IOException;
   34   import java.io.InputStream;
   35   import java.io.OutputStream;
   36   import java.io.UnsupportedEncodingException;
   37   
   38   import org.apache.commons.httpclient.ChunkedOutputStream;
   39   import org.apache.commons.httpclient.Header;
   40   import org.apache.commons.httpclient.HttpConnection;
   41   import org.apache.commons.httpclient.HttpException;
   42   import org.apache.commons.httpclient.HttpState;
   43   import org.apache.commons.httpclient.HttpVersion;
   44   import org.apache.commons.httpclient.ProtocolException;
   45   import org.apache.commons.logging.Log;
   46   import org.apache.commons.logging.LogFactory;
   47   
   48   /**
   49    * This abstract class serves as a foundation for all HTTP methods 
   50    * that can enclose an entity within requests 
   51    *
   52    * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   53    * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
   54    *
   55    * @since 2.0beta1
   56    * @version $Revision: 480424 $
   57    */
   58   public abstract class EntityEnclosingMethod extends ExpectContinueMethod {
   59   
   60       // ----------------------------------------- Static variables/initializers
   61   
   62       /**
   63        * The content length will be calculated automatically. This implies
   64        * buffering of the content.
   65        * @deprecated Use {@link InputStreamRequestEntity#CONTENT_LENGTH_AUTO}.
   66        */
   67       public static final long CONTENT_LENGTH_AUTO = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
   68   
   69       /**
   70        * The request will use chunked transfer encoding. Content length is not
   71        * calculated and the content is not buffered.<br>
   72        * @deprecated Use {@link #setContentChunked(boolean)}.
   73        */
   74       public static final long CONTENT_LENGTH_CHUNKED = -1;
   75   
   76       /** LOG object for this class. */
   77       private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
   78   
   79       /** The unbuffered request body, if any. */
   80       private InputStream requestStream = null;
   81   
   82       /** The request body as string, if any. */
   83       private String requestString = null;
   84   
   85       private RequestEntity requestEntity;
   86       
   87       /** Counts how often the request was sent to the server. */
   88       private int repeatCount = 0;
   89   
   90       /** The content length of the <code>requestBodyStream</code> or one of
   91        *  <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
   92        * 
   93        * @deprecated
   94        */
   95       private long requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
   96       
   97       private boolean chunked = false;
   98   
   99       // ----------------------------------------------------------- Constructors
  100   
  101       /**
  102        * No-arg constructor.
  103        *
  104        * @since 2.0
  105        */
  106       public EntityEnclosingMethod() {
  107           super();
  108           setFollowRedirects(false);
  109       }
  110   
  111       /**
  112        * Constructor specifying a URI.
  113        *
  114        * @param uri either an absolute or relative URI
  115        *
  116        * @since 2.0
  117        */
  118       public EntityEnclosingMethod(String uri) {
  119           super(uri);
  120           setFollowRedirects(false);
  121       }
  122   
  123       /**
  124        * Returns <tt>true</tt> if there is a request body to be sent.
  125        * 
  126        * <P>This method must be overridden by sub-classes that implement
  127        * alternative request content input methods
  128        * </p>
  129        * 
  130        * @return boolean
  131        * 
  132        * @since 2.0beta1
  133        */
  134       protected boolean hasRequestContent() {
  135           LOG.trace("enter EntityEnclosingMethod.hasRequestContent()");
  136           return (this.requestEntity != null) 
  137               || (this.requestStream != null) 
  138               || (this.requestString != null);
  139       }
  140   
  141       /**
  142        * Clears the request body.
  143        * 
  144        * <p>This method must be overridden by sub-classes that implement
  145        * alternative request content input methods.</p>
  146        * 
  147        * @since 2.0beta1
  148        */
  149       protected void clearRequestBody() {
  150           LOG.trace("enter EntityEnclosingMethod.clearRequestBody()");
  151           this.requestStream = null;
  152           this.requestString = null;
  153           this.requestEntity = null;
  154       }
  155   
  156       /**
  157        * Generates the request body.   
  158        * 
  159        * <p>This method must be overridden by sub-classes that implement
  160        * alternative request content input methods.</p>
  161        * 
  162        * @return request body as an array of bytes. If the request content 
  163        *          has not been set, returns <tt>null</tt>.
  164        * 
  165        * @since 2.0beta1
  166        */
  167       protected byte[] generateRequestBody() {
  168           LOG.trace("enter EntityEnclosingMethod.renerateRequestBody()");
  169           return null;
  170       }
  171   
  172       protected RequestEntity generateRequestEntity() {
  173           
  174           byte[] requestBody = generateRequestBody();
  175           if (requestBody != null) {
  176               // use the request body, if it exists.
  177               // this is just for backwards compatability
  178               this.requestEntity = new ByteArrayRequestEntity(requestBody);
  179           } else if (this.requestStream != null) {
  180               this.requestEntity = new InputStreamRequestEntity(
  181                   requestStream, 
  182                   requestContentLength);
  183               this.requestStream = null;
  184           } else if (this.requestString != null) {
  185               String charset = getRequestCharSet(); 
  186               try {
  187                   this.requestEntity = new StringRequestEntity(
  188                           requestString, null, charset);
  189               } catch (UnsupportedEncodingException e) {
  190                   if (LOG.isWarnEnabled()) {
  191                       LOG.warn(charset + " not supported");
  192                   }
  193                   try {
  194                       this.requestEntity = new StringRequestEntity(
  195                               requestString, null, null);
  196                   } catch (UnsupportedEncodingException ignore) {
  197                   }
  198               }
  199           }
  200   
  201           return this.requestEntity;
  202       }
  203       
  204       /**
  205        * Entity enclosing requests cannot be redirected without user intervention
  206        * according to RFC 2616.
  207        *
  208        * @return <code>false</code>.
  209        *
  210        * @since 2.0
  211        */
  212       public boolean getFollowRedirects() {
  213           return false;
  214       }
  215   
  216   
  217       /**
  218        * Entity enclosing requests cannot be redirected without user intervention 
  219        * according to RFC 2616.
  220        *
  221        * @param followRedirects must always be <code>false</code>
  222        */
  223       public void setFollowRedirects(boolean followRedirects) {
  224           if (followRedirects == true) {
  225               throw new IllegalArgumentException("Entity enclosing requests cannot be redirected without user intervention");
  226           }
  227           super.setFollowRedirects(false);
  228       }
  229   
  230       /**
  231        * Sets length information about the request body.
  232        *
  233        * <p>
  234        * Note: If you specify a content length the request is unbuffered. This
  235        * prevents redirection and automatic retry if a request fails the first
  236        * time. This means that the HttpClient can not perform authorization
  237        * automatically but will throw an Exception. You will have to set the
  238        * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
  239        * </p>
  240        *
  241        * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
  242        *        CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
  243        *        is specified the content will not be buffered internally and the
  244        *        Content-Length header of the request will be used. In this case
  245        *        the user is responsible to supply the correct content length.
  246        *        If CONTENT_LENGTH_AUTO is specified the request will be buffered
  247        *        before it is sent over the network.
  248        * 
  249        * @deprecated Use {@link #setContentChunked(boolean)} or 
  250        * {@link #setRequestEntity(RequestEntity)}
  251        */
  252       public void setRequestContentLength(int length) {
  253           LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
  254           this.requestContentLength = length;
  255       }
  256   
  257       /**
  258        * Returns the request's charset.  The charset is parsed from the request entity's 
  259        * content type, unless the content type header has been set manually. 
  260        * 
  261        * @see RequestEntity#getContentType()
  262        * 
  263        * @since 3.0
  264        */
  265       public String getRequestCharSet() {
  266           if (getRequestHeader("Content-Type") == null) {
  267               // check the content type from request entity
  268               // We can't call getRequestEntity() since it will probably call
  269               // this method.
  270               if (this.requestEntity != null) {
  271                   return getContentCharSet(
  272                       new Header("Content-Type", requestEntity.getContentType()));
  273               } else {
  274                   return super.getRequestCharSet();
  275               }
  276           } else {
  277               return super.getRequestCharSet();
  278           }
  279       }
  280   
  281       /**
  282        * Sets length information about the request body.
  283        *
  284        * <p>
  285        * Note: If you specify a content length the request is unbuffered. This
  286        * prevents redirection and automatic retry if a request fails the first
  287        * time. This means that the HttpClient can not perform authorization
  288        * automatically but will throw an Exception. You will have to set the
  289        * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
  290        * </p>
  291        *
  292        * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
  293        *        CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
  294        *        is specified the content will not be buffered internally and the
  295        *        Content-Length header of the request will be used. In this case
  296        *        the user is responsible to supply the correct content length.
  297        *        If CONTENT_LENGTH_AUTO is specified the request will be buffered
  298        *        before it is sent over the network.
  299        * 
  300        * @deprecated Use {@link #setContentChunked(boolean)} or 
  301        * {@link #setRequestEntity(RequestEntity)}
  302        */
  303       public void setRequestContentLength(long length) {
  304           LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
  305           this.requestContentLength = length;
  306       }
  307   
  308       /**
  309        * Sets whether or not the content should be chunked.
  310        * 
  311        * @param chunked <code>true</code> if the content should be chunked
  312        * 
  313        * @since 3.0
  314        */
  315       public void setContentChunked(boolean chunked) {
  316           this.chunked = chunked;
  317       }
  318       
  319       /**
  320        * Returns the length of the request body.
  321        *
  322        * @return number of bytes in the request body
  323        */
  324       protected long getRequestContentLength() {
  325           LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
  326   
  327           if (!hasRequestContent()) {
  328               return 0;
  329           }
  330           if (this.chunked) {
  331               return -1;
  332           }
  333           if (this.requestEntity == null) {
  334               this.requestEntity = generateRequestEntity(); 
  335           }
  336           return (this.requestEntity == null) ? 0 : this.requestEntity.getContentLength();
  337       }
  338   
  339       /**
  340        * Populates the request headers map to with additional 
  341        * {@link org.apache.commons.httpclient.Header headers} to be submitted to 
  342        * the given {@link HttpConnection}.
  343        *
  344        * <p>
  345        * This implementation adds tt>Content-Length</tt> or <tt>Transfer-Encoding</tt>
  346        * headers.
  347        * </p>
  348        *
  349        * <p>
  350        * Subclasses may want to override this method to to add additional
  351        * headers, and may choose to invoke this implementation (via
  352        * <tt>super</tt>) to add the "standard" headers.
  353        * </p>
  354        *
  355        * @param state the {@link HttpState state} information associated with this method
  356        * @param conn the {@link HttpConnection connection} used to execute
  357        *        this HTTP method
  358        *
  359        * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
  360        *                     can be recovered from.
  361        * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
  362        *                    cannot be recovered from.
  363        *
  364        * @see #writeRequestHeaders
  365        * 
  366        * @since 3.0
  367        */
  368       protected void addRequestHeaders(HttpState state, HttpConnection conn)
  369       throws IOException, HttpException {
  370           LOG.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, "
  371               + "HttpConnection)");
  372   
  373           super.addRequestHeaders(state, conn);
  374           addContentLengthRequestHeader(state, conn);
  375   
  376           // only use the content type of the request entity if it has not already been
  377           // set manually
  378           if (getRequestHeader("Content-Type") == null) {
  379               RequestEntity requestEntity = getRequestEntity();
  380               if (requestEntity != null && requestEntity.getContentType() != null) {
  381                   setRequestHeader("Content-Type", requestEntity.getContentType());
  382               }
  383           }
  384       }
  385       
  386       /**
  387        * Generates <tt>Content-Length</tt> or <tt>Transfer-Encoding: Chunked</tt>
  388        * request header, as long as no <tt>Content-Length</tt> request header
  389        * already exists.
  390        *
  391        * @param state current state of http requests
  392        * @param conn the connection to use for I/O
  393        *
  394        * @throws IOException when errors occur reading or writing to/from the
  395        *         connection
  396        * @throws HttpException when a recoverable error occurs
  397        */
  398       protected void addContentLengthRequestHeader(HttpState state,
  399                                                    HttpConnection conn)
  400       throws IOException, HttpException {
  401           LOG.trace("enter EntityEnclosingMethod.addContentLengthRequestHeader("
  402                     + "HttpState, HttpConnection)");
  403   
  404           if ((getRequestHeader("content-length") == null) 
  405               && (getRequestHeader("Transfer-Encoding") == null)) {
  406               long len = getRequestContentLength();
  407               if (len < 0) {
  408                   if (getEffectiveVersion().greaterEquals(HttpVersion.HTTP_1_1)) {
  409                       addRequestHeader("Transfer-Encoding", "chunked");
  410                   } else {
  411                       throw new ProtocolException(getEffectiveVersion() + 
  412                           " does not support chunk encoding");
  413                   }
  414               } else {
  415                   addRequestHeader("Content-Length", String.valueOf(len));
  416               }
  417           }
  418       }
  419   
  420       /**
  421        * Sets the request body to be the specified inputstream.
  422        *
  423        * @param body Request body content as {@link java.io.InputStream}
  424        * 
  425        * @deprecated use {@link #setRequestEntity(RequestEntity)}
  426        */
  427       public void setRequestBody(InputStream body) {
  428           LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
  429           clearRequestBody();
  430           this.requestStream = body;
  431       }
  432   
  433       /**
  434        * Sets the request body to be the specified string.
  435        * The string will be submitted, using the encoding
  436        * specified in the Content-Type request header.<br>
  437        * Example: <code>setRequestHeader("Content-type", "text/xml; charset=UTF-8");</code><br>
  438        * Would use the UTF-8 encoding.
  439        * If no charset is specified, the 
  440        * {@link org.apache.commons.httpclient.HttpConstants#DEFAULT_CONTENT_CHARSET default}
  441        * content encoding is used (ISO-8859-1).
  442        *
  443        * @param body Request body content as a string
  444        * 
  445        * @deprecated use {@link #setRequestEntity(RequestEntity)}
  446        */
  447       public void setRequestBody(String body) {
  448           LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
  449           clearRequestBody();
  450           this.requestString = body;
  451       }
  452   
  453       /**
  454        * Writes the request body to the given {@link HttpConnection connection}.
  455        *
  456        * @param state the {@link HttpState state} information associated with this method
  457        * @param conn the {@link HttpConnection connection} used to execute
  458        *        this HTTP method
  459        *
  460        * @return <tt>true</tt>
  461        *
  462        * @throws IOException if an I/O (transport) error occurs. Some transport exceptions
  463        *                     can be recovered from.
  464        * @throws HttpException  if a protocol exception occurs. Usually protocol exceptions 
  465        *                    cannot be recovered from.
  466        */
  467       protected boolean writeRequestBody(HttpState state, HttpConnection conn)
  468       throws IOException, HttpException {
  469           LOG.trace(
  470               "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
  471           
  472           if (!hasRequestContent()) {
  473               LOG.debug("Request body has not been specified");
  474               return true;
  475           }
  476           if (this.requestEntity == null) {
  477               this.requestEntity = generateRequestEntity(); 
  478           }
  479           if (requestEntity == null) {
  480               LOG.debug("Request body is empty");
  481               return true;
  482           }
  483   
  484           long contentLength = getRequestContentLength();
  485   
  486           if ((this.repeatCount > 0) && !requestEntity.isRepeatable()) {
  487               throw new ProtocolException(
  488                   "Unbuffered entity enclosing request can not be repeated.");
  489           }
  490   
  491           this.repeatCount++;
  492   
  493           OutputStream outstream = conn.getRequestOutputStream();
  494           
  495           if (contentLength < 0) {
  496               outstream = new ChunkedOutputStream(outstream);
  497           }
  498           
  499           requestEntity.writeRequest(outstream);
  500           
  501           // This is hardly the most elegant solution to closing chunked stream
  502           if (outstream instanceof ChunkedOutputStream) {
  503               ((ChunkedOutputStream) outstream).finish();
  504           }
  505           
  506           outstream.flush();
  507           
  508           LOG.debug("Request body sent");
  509           return true;
  510       }
  511   
  512       /**
  513        * Recycles the HTTP method so that it can be used again.
  514        * Note that all of the instance variables will be reset
  515        * once this method has been called. This method will also
  516        * release the connection being used by this HTTP method.
  517        * 
  518        * @see #releaseConnection()
  519        * 
  520        * @deprecated no longer supported and will be removed in the future
  521        *             version of HttpClient
  522        */
  523       public void recycle() {
  524           LOG.trace("enter EntityEnclosingMethod.recycle()");
  525           clearRequestBody();
  526           this.requestContentLength = InputStreamRequestEntity.CONTENT_LENGTH_AUTO;
  527           this.repeatCount = 0;
  528           this.chunked = false;
  529           super.recycle();
  530       }
  531   
  532       /**
  533        * @return Returns the requestEntity.
  534        * 
  535        * @since 3.0
  536        */
  537       public RequestEntity getRequestEntity() {
  538           return generateRequestEntity();
  539       }
  540   
  541       /**
  542        * @param requestEntity The requestEntity to set.
  543        * 
  544        * @since 3.0
  545        */
  546       public void setRequestEntity(RequestEntity requestEntity) {
  547           clearRequestBody();
  548           this.requestEntity = requestEntity;
  549       }
  550   
  551   }

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