You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ol...@apache.org on 2003/02/01 01:01:02 UTC

cvs commit: jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods EntityEnclosingMethod.java

olegk       2003/01/31 16:01:01

  Added:       httpclient/src/java/org/apache/commons/httpclient/methods
                        EntityEnclosingMethod.java
  Log:
  Bug fixes:
  
  http://nagoya.apache.org/bugzilla/show_bug.cgi?id=11095
  http://nagoya.apache.org/bugzilla/show_bug.cgi?id=11653
  http://nagoya.apache.org/bugzilla/show_bug.cgi?id=14731
  
  Changelog:
  
  - Abstract EntityEnclosingMethod class has been introduced to encapsulate common behaviour of all entity enclosing methods
  - "Expect: 100-continue" header support
  - HttpClient does not hang indefinitely if the server of origin does not return status code 100 when expected. HttpClient resumes sending request body after 3 seconds if no response is sent
  - Support for chunk encoded requests in all entity enclosing methods
  - Entity enclosing methods do not allow automatic redirection
  - More robust (or so I'd like to hope) request content buffering logic
  - PostMethod inherited from EntityEnclosingMethod class
  - PutMethod inherited from EntityEnclosingMethod class
  - Older methods of PostMethod dealing with url-encoded parameters deprecated in favour of new setRequestBody(NameValuePair[]) method
  
  Submitted by Oleg Kalnichevski
  
  Revision  Changes    Path
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java
  
  Index: EntityEnclosingMethod.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v 1.1 2003/02/01 00:01:01 olegk Exp $
   * $Revision: 1.1 $
   * $Date: 2003/02/01 00:01:01 $
   * ====================================================================
   *
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   *
   * [Additional notices, if required by prior licensing conditions]
   *
   */
  
  package org.apache.commons.httpclient.methods;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.io.OutputStream;
  import java.io.ByteArrayInputStream;
  import java.io.Reader;
  import java.io.InputStreamReader;
  import java.io.UnsupportedEncodingException;
  import java.io.ByteArrayOutputStream;
  
  import org.apache.commons.httpclient.HttpConstants;
  import org.apache.commons.httpclient.HttpConnection;
  import org.apache.commons.httpclient.HttpException;
  import org.apache.commons.httpclient.HttpState;
  import org.apache.commons.httpclient.HttpStatus;
  import org.apache.commons.httpclient.ChunkedOutputStream;
  import org.apache.commons.httpclient.ContentLengthInputStream;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * This abstract class serves as a foundation for all HTTP methods 
   * that can enclose an entity within requests 
   *
   * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
   * @author <a href="mailto:dsale@us.britannica.com">Doug Sale</a>
   * @author <a href="mailto:jsdever@apache.org">Jeff Dever</a>
   * @author Ortwin Gl�ck
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   * 
   * @since 2.0beta1
   */
  
  public abstract class EntityEnclosingMethod extends GetMethod
  {
      // ----------------------------------------- Static variables/initializers
  
      /**
       * The content length will be calculated automatically. This implies
       * buffering of the content.
       */
      public static final int CONTENT_LENGTH_AUTO = -2;
  
      /**
       * The request will use chunked transfer encoding. Content length is not
       * calculated and the content is not buffered.<br>
       */
      public static final int CONTENT_LENGTH_CHUNKED = -1;
  
      /** LOG object for this class. */
      private static final Log LOG = LogFactory.getLog(EntityEnclosingMethod.class);
  
      /** The buffered request body, if any. */
      private byte[] buffer = null;
  
      /** The unbuffered request body, if any. */
      private InputStream requestBodyStream = null;
  
      /** Counts how often the request was sent to the server. */
      private int repeatCount = 0;
  
      /** The content length of the <code>requestBodyStream</code> or one of
       *  <code>CONTENT_LENGTH_AUTO</code> and <code>CONTENT_LENGTH_CHUNKED</code>.
       */
      private int requestContentLength = CONTENT_LENGTH_AUTO;
  
      private boolean useExpectHeader = true;
      
      // ----------------------------------------------------------- Constructors
  
      /**
       * No-arg constructor.
       *
       * @since 2.0
       */
      public EntityEnclosingMethod() {
          super();
          setFollowRedirects(false);
      }
  
      /**
       * Constructor specifying a URI.
       *
       * @param uri either an absolute or relative URI
       *
       * @since 2.0
       */
      public EntityEnclosingMethod(String uri) {
          super(uri);
          setFollowRedirects(false);
      }
  
      /**
       * Constructor specifying a URI and a tempDir.
       *
       * @param uri either an absolute or relative URI
       * @param tempDir directory to store temp files in
       *
       * @since 2.0
       */
      public EntityEnclosingMethod(String uti, String tempDir) {
          super(uti, tempDir);
          setFollowRedirects(false);
      }
  
      /**
       * Constructor specifying a URI, tempDir and tempFile.
       *
       * @param uri either an absolute or relative URI
       * @param tempDir directory to store temp files in
       * @param tempFile file to store temporary data in
       *
       * @since 2.0
       */
      public EntityEnclosingMethod(String uri, String tempDir, String tempFile) {
          super(uri, tempDir, tempFile);
          setFollowRedirects(false);
      }
  
  
      /**
       * Entity enclosing requests cannot be redirected without user intervention according to RFC 2616.
       *
       * @return <code>false</code>.
       *
       * @since 2.0
       */
      public boolean getFollowRedirects() {
          return false;
      }
  
  
      /**
       * Entity enclosing requests cannot be redirected without user intervention according to RFC 2616.
       *
       * @param followRedirects must always be <code>false</code>
       * 
       * @throws IllegalArgumentException if <code>true</code> is given
       */
      public void setFollowRedirects(boolean followRedirects) {
          if (followRedirects == true) {
              // TODO: EntityEnclosingMethod should inherit from HttpMethodBase rather than GetMethod
              // Enable exception once the inheritence is fixed
              //throw new IllegalArgumentException(
              //  "Entity enclosing requests cannot be redirected without user intervention");
              LOG.warn("Entity enclosing methods may not redirect requests");
          }
          super.setFollowRedirects(false);
      }
  
  
      /**
       * Returns the useExpectHeader.
       * @return boolean
       */
      public boolean getUseExpectHeader()
      {
          return this.useExpectHeader;
      }
  
      /**
       * Sets the useExpectHeader.
       * @param useExpectHeader The useExpectHeader to set
       */
      public void setUseExpectHeader(boolean value)
      {
          this.useExpectHeader = value;
      }
  
      /**
       * Sets length information about the request body.
       *
       * <p>
       * Note: If you specify a content length the request is unbuffered. This
       * prevents redirection and automatic retry if a request fails the first
       * time. This means that the HttpClient can not perform authorization
       * automatically but will throw an Exception. You will have to set the
       * necessary 'Authorization' or 'Proxy-Authorization' headers manually.
       * </p>
       *
       * @param length size in bytes or any of CONTENT_LENGTH_AUTO,
       *        CONTENT_LENGTH_CHUNKED. If number of bytes or CONTENT_LENGTH_CHUNKED
       *        is specified the content will not be buffered internally and the
       *        Content-Length header of the request will be used. In this case
       *        the user is responsible to supply the correct content length.
       *        If CONTENT_LENGTH_AUTO is specified the request will be buffered
       *        before it is sent over the network.
       *
       * @since 2.0
       */
      public void setRequestContentLength(int length) {
          LOG.trace("enter EntityEnclosingMethod.setRequestContentLength(int)");
          this.requestContentLength = length;
      }
  
      /**
       * Override method of {@link org.apache.commons.httpclient.HttpMethodBase}
       * to return the length of the request body.
       *
       * @return number of bytes in the request body
       *
       * @since 2.0
       */
      protected int getRequestContentLength() {
          LOG.trace("enter EntityEnclosingMethod.getRequestContentLength()");
  
          if (this.requestContentLength != CONTENT_LENGTH_AUTO) {
              return this.requestContentLength;
          }
          bufferContent();
          if (this.buffer != null) {
              return this.buffer.length;
          }
          else {
              return 0;
          }
      }
  
      /**
       * Sets the request body to be the specified inputstream.
       *
       * @param body Request body content as {@link java.io.InputStream}
       *
       * @since 2.0
       */
      public void setRequestBody(InputStream body) {
          LOG.trace("enter EntityEnclosingMethod.setRequestBody(InputStream)");
          this.requestBodyStream = body;
          this.buffer = null;
      }
  
      /**
       * Gets the request body as a stream.
       *
       * @return The request body {@link java.io.InputStream} if it has been set.
       *
       * @throws IllegalStateException if request body is not buferred
       * 
       * @since 2.0
       */
      public InputStream getRequestBody() {
          LOG.trace("enter EntityEnclosingMethod.getRequestBody()");
          if (this.buffer != null) {
              return new ByteArrayInputStream(this.buffer); 
          }
          else {
              return this.requestBodyStream;
          }
      }
  
      /**
       * Sets the request body to be the specified string.
       *
       * @param body Request body content as a string
       *
       * @since 2.0
       */
      public void setRequestBody(String body) {
          LOG.trace("enter EntityEnclosingMethod.setRequestBody(String)");
  
          if (body == null) {
              this.requestBodyStream = null;
              this.buffer = null;
              return;
          }
          this.buffer = HttpConstants.getContentBytes(body, getRequestCharSet());
      }
  
      /**
       * Gets the request body as a string.
       *
       * @return the request body as a string
       * 
       * @throws IOException when i/o errors occur reading the request
       * @throws IllegalStateException if request body is not buferred
       *
       * @since 2.0
       */
  
      public String getRequestBodyAsString() throws IOException {
          LOG.trace("enter EntityEnclosingMethod.getRequestBodyAsString()");
          Reader instream = null;
          try {
              instream = new InputStreamReader(getRequestBody(), getRequestCharSet());
          }
          catch(UnsupportedEncodingException e) {
              if (LOG.isWarnEnabled()) {
                  LOG.warn("Unsupported encoding: " + e.getMessage());
              }
              instream = new InputStreamReader(getRequestBody());
          }
          StringBuffer buffer = new StringBuffer();
          char[] tmp = new char[4096];
          int l = 0;
          while((l = instream.read(tmp)) >= 0) {
              buffer.append(tmp, 0, l);
          }
          return buffer.toString();
      }
  
  
      /**
       * Override the method of {@link HttpMethodBase}
       * to set the <tt>Expect</tt> header if it has
       * not already been set, in addition to the "standard"
       * set of headers.
       *
       * @throws HttpException when a protocol error occurs or state is invalid
       * @throws IOException when i/o errors occur reading the response
       * 
       * @since 2.0
       */
  
      protected void addRequestHeaders(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          LOG.trace("enter EntityEnclosingMethod.addRequestHeaders(HttpState, HttpConnection)");
          
          if (!isHttp11() && getUseExpectHeader()) {
              LOG.warn("100-continue not allowed for HTTP/1.0");
          }
  
          super.addRequestHeaders(state, conn);
          // Send expectation header, provided there's something to be sent
          if(isHttp11() && getUseExpectHeader() && (this.requestBodyStream != null)) {
              if (getRequestHeader("Expect") == null) {
                  setRequestHeader("Expect", "100-continue");
              }
          }
      }
  
      /**
       * Override method of {@link org.apache.commons.httpclient.HttpMethodBase}
       * to write request parameters as the request body.  The input stream will
       * be truncated after the specified content length.
       *
       * @param state the client state
       * @param conn the connection to write to
       *
       * @return <tt>true</tt>
       * @throws IOException when i/o errors occur reading the response
       * @throws HttpException when a protocol error occurs or state is invalid
       *
       * @since 2.0
       */
      protected boolean writeRequestBody(HttpState state, HttpConnection conn)
      throws IOException, HttpException {
          LOG.trace(
              "enter EntityEnclosingMethod.writeRequestBody(HttpState, HttpConnection)");
          
          if (getRequestHeader("Expect") != null) {
              if (getStatusLine() == null) {
                  LOG.debug("Expecting response");
                  return false;
              }
              if(getStatusLine().getStatusCode() != HttpStatus.SC_CONTINUE) {
                  LOG.debug("Expecting 100-continue");
                  return false;
              }
          }
  
          int contentLength = getRequestContentLength();
  
          if ((contentLength == CONTENT_LENGTH_CHUNKED) && !isHttp11()) {
              throw new HttpException(
                  "Chunked transfer encoding not allowed for HTTP/1.0");
          }
          InputStream instream = getRequestBody();
          if (instream == null) {
              LOG.debug("Request body is empty");
              return true;
          }
  
          if ((this.repeatCount > 0) && (this.buffer == null)) {
              throw new HttpException(
                  "Unbuffered entity enclosing request can not be repeated.");
          }
  
          this.repeatCount++;
  
          OutputStream outstream = conn.getRequestOutputStream();
          if (contentLength == CONTENT_LENGTH_CHUNKED) {
              outstream = new ChunkedOutputStream(outstream);
          }
          if (contentLength >= 0) {
              // don't need a watcher here - we're reading from something local,
              // not server-side.
              instream = new ContentLengthInputStream(instream, contentLength);
          }
  
          byte[] tmp = new byte[4096];
          int total = 0;
          int i = 0;
          while ((i = instream.read(tmp)) >= 0) {
              outstream.write(tmp, 0, i);
              total += i;
          }
          // This is hardly the most elegant solution to closing chunked stream
          if (outstream instanceof ChunkedOutputStream) {
              ((ChunkedOutputStream)outstream).writeClosingChunk();
          }
          if ((contentLength > 0) && (total < contentLength)) {
              throw new IOException("Unexpected end of input stream after "
                  + total + " bytes (expected " + contentLength + " bytes)");
          }
          LOG.debug("Request body sent");
          return true;
      }
  
      /**
       * Override method of {@link org.apache.commons.httpclient.HttpMethodBase}
       * to clear my request body.
       *
       * @since 2.0
       */
      public void recycle() {
          LOG.trace("enter EntityEnclosingMethod.recycle()");
          super.recycle();
          this.requestContentLength = CONTENT_LENGTH_AUTO;
          this.requestBodyStream = null;
          this.buffer = null;
          this.repeatCount = 0;
      }
  
      /**
       * Buffers the request body and calculates the content length. If the
       * method was called earlier it returns immediately.
       *
       * @since 2.0
       */
      
      protected void bufferContent() {
          LOG.trace("enter EntityEnclosingMethod.bufferContent()");
  
          if (this.buffer != null) {
              return;
          }
          if (this.requestBodyStream == null) {
              return;
          }
          try {
              ByteArrayOutputStream tmp = new ByteArrayOutputStream();
              byte[] data = new byte[4096];
              int l = 0;
              while ((l = this.requestBodyStream.read(data)) >= 0) {
                  tmp.write(data, 0, l);
              }
              this.buffer = tmp.toByteArray();
              this.requestBodyStream = null;
          } catch (IOException e) {
              if (LOG.isErrorEnabled()) {
                  LOG.error(e.toString(), e );
              }
              this.buffer = null;
              this.requestBodyStream = null;
          }
      }
  }
  
  
  

---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org