View Javadoc

    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 }