commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From mbe...@apache.org
Subject cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server SimpleHttpServerConnection.java SimpleRequest.java
Date Wed, 06 Oct 2004 03:39:59 GMT
mbecke      2004/10/05 20:39:59

  Modified:    httpclient/src/test/org/apache/commons/httpclient
                        TestWebapp.java TestEffectiveHttpVersion.java
                        TestNoHost.java
               httpclient/src/java/org/apache/commons/httpclient/methods/multipart
                        Part.java
               httpclient/xdocs preference-api.xml
               httpclient/src/java/org/apache/commons/httpclient/params
                        HttpMethodParams.java
               httpclient/src/examples MultipartFileUploadApp.java
               httpclient/src/java/org/apache/commons/httpclient/methods
                        MultipartPostMethod.java
               httpclient/src/test/org/apache/commons/httpclient/server
                        SimpleHttpServerConnection.java SimpleRequest.java
  Added:       httpclient/src/test/org/apache/commons/httpclient
                        EchoService.java TestMultipartPost.java
                        SimpleChunkedInputStream.java
               httpclient/src/java/org/apache/commons/httpclient/methods/multipart
                        MultipartRequestEntity.java
  Removed:     httpclient/src/test/org/apache/commons/httpclient
                        TestWebappMultiPostMethod.java
  Log:
  Moves multipart requests to a new MultipartRequestEntity.
  
  PR: 31378
  Submitted by: Michael Becke
  Reviewed by: Ortwin Gl?ck and Oleg Kalnichevski
  
  Revision  Changes    Path
  1.12      +4 -5      jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java
  
  Index: TestWebapp.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestWebapp.java,v
  retrieving revision 1.11
  retrieving revision 1.12
  diff -u -r1.11 -r1.12
  --- TestWebapp.java	2 Oct 2004 21:22:03 -0000	1.11
  +++ TestWebapp.java	6 Oct 2004 03:39:58 -0000	1.12
  @@ -66,7 +66,6 @@
           suite.addTest(TestWebappHeaders.suite());
           suite.addTest(TestWebappBasicAuth.suite());
           suite.addTest(TestWebappPostMethod.suite());
  -        suite.addTest(TestWebappMultiPostMethod.suite());
           suite.addTest(TestWebappNoncompliant.suite());
           suite.addTest(TestProxy.suite());
           return suite;
  
  
  
  1.3       +4 -23     jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java
  
  Index: TestEffectiveHttpVersion.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestEffectiveHttpVersion.java,v
  retrieving revision 1.2
  retrieving revision 1.3
  diff -u -r1.2 -r1.3
  --- TestEffectiveHttpVersion.java	14 Sep 2004 15:50:40 -0000	1.2
  +++ TestEffectiveHttpVersion.java	6 Oct 2004 03:39:58 -0000	1.3
  @@ -36,9 +36,6 @@
   
   import org.apache.commons.httpclient.methods.GetMethod;
   import org.apache.commons.httpclient.params.HttpMethodParams;
  -import org.apache.commons.httpclient.server.HttpService;
  -import org.apache.commons.httpclient.server.SimpleRequest;
  -import org.apache.commons.httpclient.server.SimpleResponse;
   
   /**
    * HTTP protocol versioning tests.
  @@ -64,22 +61,6 @@
   
       public static Test suite() {
           return new TestSuite(TestEffectiveHttpVersion.class);
  -    }
  -
  -    private class EchoService implements HttpService {
  -
  -        public EchoService() {
  -            super();
  -        }
  -
  -        public boolean process(final SimpleRequest request, final SimpleResponse response)
  -            throws IOException
  -        {
  -            HttpVersion httpversion = request.getRequestLine().getHttpVersion();
  -            response.setStatusLine(httpversion, HttpStatus.SC_OK);
  -            response.setBodyString(request.getBodyString());
  -            return true;
  -        }
       }
   
       public void testClientLevelHttpVersion() throws IOException {
  
  
  
  1.41      +5 -4      jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java
  
  Index: TestNoHost.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestNoHost.java,v
  retrieving revision 1.40
  retrieving revision 1.41
  diff -u -r1.40 -r1.41
  --- TestNoHost.java	28 Sep 2004 21:08:48 -0000	1.40
  +++ TestNoHost.java	6 Oct 2004 03:39:58 -0000	1.41
  @@ -91,6 +91,7 @@
           suite.addTestSuite(TestIdleConnectionTimeout.class);
           suite.addTest(TestMethodAbort.suite());
           suite.addTest(TestHttpParams.suite());
  +        suite.addTest(TestMultipartPost.suite());
           return suite;
       }
   
  
  
  
  1.1                  jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/EchoService.java
  
  Index: EchoService.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/EchoService.java,v 1.1 2004/10/06 03:39:58 mbecke Exp $
   * $Revision: 1.1 $
   * $Date: 2004/10/06 03:39:58 $
   *
   * ====================================================================
   *
   *  Copyright 2002-2004 The Apache Software Foundation
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   * ====================================================================
   *
   * 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/>.
   *
   */
  package org.apache.commons.httpclient;
  
  import java.io.IOException;
  
  import org.apache.commons.httpclient.server.HttpService;
  import org.apache.commons.httpclient.server.SimpleRequest;
  import org.apache.commons.httpclient.server.SimpleResponse;
  
  
  /**
   * A service that echos the request body.
   */
  class EchoService implements HttpService {
  
      public EchoService() {
          super();
      }
  
      public boolean process(final SimpleRequest request, final SimpleResponse response)
          throws IOException
      {
          HttpVersion httpversion = request.getRequestLine().getHttpVersion();
          response.setStatusLine(httpversion, HttpStatus.SC_OK);
          if (request.containsHeader("Content-Length")) {
              response.addHeader(request.getFirstHeader("Content-Length"));            
          }
          if (request.containsHeader("Content-Type")) {
              response.addHeader(request.getFirstHeader("Content-Type"));            
          }
          response.setBodyString(request.getBodyString());
          return true;
      }
  }
  
  
  
  1.1                  jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMultipartPost.java
  
  Index: TestMultipartPost.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestMultipartPost.java,v 1.1 2004/10/06 03:39:58 mbecke Exp $
   * $Revision: 1.1 $
   * $Date: 2004/10/06 03:39:58 $
   *
   * ====================================================================
   *
   *  Copyright 2003-2004 The Apache Software Foundation
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   * ====================================================================
   *
   * 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;
  
  import junit.framework.Test;
  import junit.framework.TestSuite;
  
  import org.apache.commons.httpclient.methods.PostMethod;
  import org.apache.commons.httpclient.methods.multipart.ByteArrayPartSource;
  import org.apache.commons.httpclient.methods.multipart.FilePart;
  import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
  import org.apache.commons.httpclient.methods.multipart.Part;
  import org.apache.commons.httpclient.methods.multipart.StringPart;
  
  /**
   * Webapp tests specific to the MultiPostMethod.
   *
   * @author <a href="oleg@ural.ru">Oleg Kalnichevski</a>
   */
  public class TestMultipartPost extends HttpClientTestBase {
  
      public TestMultipartPost(String testName) {
          super(testName);
      }
  
      public static Test suite() {
          TestSuite suite = new TestSuite(TestMultipartPost.class);
          return suite;
      }
  
      public static void main(String args[]) {
          String[] testCaseName = { TestMultipartPost.class.getName() };
          junit.textui.TestRunner.main(testCaseName);
      }
  
      // ------------------------------------------------------------------ Tests
      
      /**
       * Test that the body consisting of a string part can be posted.
       */
      public void testPostStringPart() throws Exception {
          
          this.server.setHttpService(new EchoService());
          
          PostMethod method = new PostMethod();
          MultipartRequestEntity entity = new MultipartRequestEntity(
              new Part[] { new StringPart("param", "Hello", "ISO-8859-1") },
              method.getParams());
          method.setRequestEntity(entity);
          client.executeMethod(method);
  
          assertEquals(200,method.getStatusCode());
          String body = method.getResponseBodyAsString();
          assertTrue(body.indexOf("Content-Disposition: form-data; name=\"param\"") >= 0);
          assertTrue(body.indexOf("Content-Type: text/plain; charset=ISO-8859-1") >= 0);
          assertTrue(body.indexOf("Content-Transfer-Encoding: 8bit") >= 0);
          assertTrue(body.indexOf("Hello") >= 0);
      }
  
  
      /**
       * Test that the body consisting of a file part can be posted.
       */
      public void testPostFilePart() throws Exception {
          
          this.server.setHttpService(new EchoService());
  
          PostMethod method = new PostMethod();
          byte[] content = "Hello".getBytes();
          MultipartRequestEntity entity = new MultipartRequestEntity(
              new Part[] { 
                  new FilePart(
                      "param1", 
                      new ByteArrayPartSource("filename.txt", content), 
                      "text/plain", 
                      "ISO-8859-1") },
              method.getParams());
          method.setRequestEntity(entity);
  
          client.executeMethod(method);
  
          assertEquals(200,method.getStatusCode());
          String body = method.getResponseBodyAsString();
          assertTrue(body.indexOf("Content-Disposition: form-data; name=\"param1\"; filename=\"filename.txt\"") >= 0);
          assertTrue(body.indexOf("Content-Type: text/plain; charset=ISO-8859-1") >= 0);
          assertTrue(body.indexOf("Content-Transfer-Encoding: binary") >= 0);
          assertTrue(body.indexOf("Hello") >= 0);
      }
  }
  
  
  
  
  1.1                  jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleChunkedInputStream.java
  
  Index: SimpleChunkedInputStream.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/SimpleChunkedInputStream.java,v 1.1 2004/10/06 03:39:58 mbecke Exp $
   * $Revision: 1.1 $
   * $Date: 2004/10/06 03:39:58 $
   *
   * ====================================================================
   *
   *  Copyright 2002-2004 The Apache Software Foundation
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   * ====================================================================
   *
   * 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/>.
   *
   */
  
  package org.apache.commons.httpclient;
  
  import java.io.ByteArrayOutputStream;
  import java.io.IOException;
  import java.io.InputStream;
  
  import org.apache.commons.httpclient.util.EncodingUtil;
  import org.apache.commons.httpclient.util.ExceptionUtil;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  
  /**
   * <p>Transparently coalesces chunks of a HTTP stream that uses
   * Transfer-Encoding chunked.</p>
   *
   * <p>Note that this class NEVER closes the underlying stream, even when close
   * gets called.  Instead, it will read until the "end" of its chunking on close,
   * which allows for the seamless invocation of subsequent HTTP 1.1 calls, while
   * not requiring the client to remember to read the entire contents of the
   * response.</p>
   *
   * @author Ortwin Gl?ck
   * @author Sean C. Sullivan
   * @author Martin Elwin
   * @author Eric Johnson
   * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
   * @author Michael Becke
   * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
   *
   * @since 2.0
   *
   */
  public class SimpleChunkedInputStream extends InputStream {
      /** The inputstream that we're wrapping */
      private InputStream in;
  
      /** The chunk size */
      private int chunkSize;
  
      /** The current position within the current chunk */
      private int pos;
  
      /** True if we'are at the beginning of stream */
      private boolean bof = true;
  
      /** True if we've reached the end of stream */
      private boolean eof = false;
  
      /** True if this stream is closed */
      private boolean closed = false;
  
      private String elementCharset = null;
      
      /** Log object for this class. */
      private static final Log LOG = LogFactory.getLog(ChunkedInputStream.class);
      /**
       *
       *
       * @param in must be non-null
       * @param method must be non-null
       *
       * @throws IOException If an IO error occurs
       */
      public SimpleChunkedInputStream(
          final InputStream in, final String elementCharset) throws IOException {
              
          if (in == null) {
              throw new IllegalArgumentException("InputStream parameter may not be null");
          }
          this.elementCharset = elementCharset;
          this.in = in;
          this.pos = 0;
      }
  
      /**
       * <p> Returns all the data in a chunked stream in coalesced form. A chunk
       * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0
       * is detected.</p>
       * 
       * <p> Trailer headers are read automcatically at the end of the stream and
       * can be obtained with the getResponseFooters() method.</p>
       *
       * @return -1 of the end of the stream has been reached or the next data
       * byte
       * @throws IOException If an IO problem occurs
       * 
       * @see HttpMethod#getResponseFooters()
       */
      public int read() throws IOException {
  
          if (closed) {
              throw new IOException("Attempted read from closed stream.");
          }
          if (eof) {
              return -1;
          } 
          if (pos >= chunkSize) {
              nextChunk();
              if (eof) { 
                  return -1;
              }
          }
          pos++;
          return in.read();
      }
  
      /**
       * Read some bytes from the stream.
       * @param b The byte array that will hold the contents from the stream.
       * @param off The offset into the byte array at which bytes will start to be
       * placed.
       * @param len the maximum number of bytes that can be returned.
       * @return The number of bytes returned or -1 if the end of stream has been
       * reached.
       * @see java.io.InputStream#read(byte[], int, int)
       * @throws IOException if an IO problem occurs.
       */
      public int read (byte[] b, int off, int len) throws IOException {
  
          if (closed) {
              throw new IOException("Attempted read from closed stream.");
          }
  
          if (eof) { 
              return -1;
          }
          if (pos >= chunkSize) {
              nextChunk();
              if (eof) { 
                  return -1;
              }
          }
          len = Math.min(len, chunkSize - pos);
          int count = in.read(b, off, len);
          pos += count;
          return count;
      }
  
      /**
       * Read some bytes from the stream.
       * @param b The byte array that will hold the contents from the stream.
       * @return The number of bytes returned or -1 if the end of stream has been
       * reached.
       * @see java.io.InputStream#read(byte[])
       * @throws IOException if an IO problem occurs.
       */
      public int read (byte[] b) throws IOException {
          return read(b, 0, b.length);
      }
  
      /**
       * Read the CRLF terminator.
       * @throws IOException If an IO error occurs.
       */
      private void readCRLF() throws IOException {
          int cr = in.read();
          int lf = in.read();
          if ((cr != '\r') || (lf != '\n')) { 
              throw new IOException(
                  "CRLF expected at end of chunk: " + cr + "/" + lf);
          }
      }
  
  
      /**
       * Read the next chunk.
       * @throws IOException If an IO error occurs.
       */
      private void nextChunk() throws IOException {
          if (!bof) {
              readCRLF();
          }
          chunkSize = getChunkSizeFromInputStream(in);
          bof = false;
          pos = 0;
          if (chunkSize == 0) {
              eof = true;
              parseTrailerHeaders();
          }
      }
  
      /**
       * Expects the stream to start with a chunksize in hex with optional
       * comments after a semicolon. The line must end with a CRLF: "a3; some
       * comment\r\n" Positions the stream at the start of the next line.
       *
       * @param in The new input stream.
       * @param required <tt>true<tt/> if a valid chunk must be present,
       *                 <tt>false<tt/> otherwise.
       * 
       * @return the chunk size as integer
       * 
       * @throws IOException when the chunk size could not be parsed
       */
      private static int getChunkSizeFromInputStream(final InputStream in) 
        throws IOException {
              
          ByteArrayOutputStream baos = new ByteArrayOutputStream();
          // States: 0=normal, 1=\r was scanned, 2=inside quoted string, -1=end
          int state = 0; 
          while (state != -1) {
          int b = in.read();
              if (b == -1) { 
                  throw new IOException("chunked stream ended unexpectedly");
              }
              switch (state) {
                  case 0: 
                      switch (b) {
                          case '\r':
                              state = 1;
                              break;
                          case '\"':
                              state = 2;
                              /* fall through */
                          default:
                              baos.write(b);
                      }
                      break;
  
                  case 1:
                      if (b == '\n') {
                          state = -1;
                      } else {
                          // this was not CRLF
                          throw new IOException("Protocol violation: Unexpected"
                              + " single newline character in chunk size");
                      }
                      break;
  
                  case 2:
                      switch (b) {
                          case '\\':
                              b = in.read();
                              baos.write(b);
                              break;
                          case '\"':
                              state = 0;
                              /* fall through */
                          default:
                              baos.write(b);
                      }
                      break;
                  default: throw new RuntimeException("assertion failed");
              }
          }
  
          //parse data
          String dataString = EncodingUtil.getAsciiString(baos.toByteArray());
          int separator = dataString.indexOf(';');
          dataString = (separator > 0)
              ? dataString.substring(0, separator).trim()
              : dataString.trim();
  
          int result;
          try {
              result = Integer.parseInt(dataString.trim(), 16);
          } catch (NumberFormatException e) {
              throw new IOException ("Bad chunk size: " + dataString);
          }
          return result;
      }
  
      /**
       * Reads and stores the Trailer headers.
       * @throws IOException If an IO problem occurs
       */
      private void parseTrailerHeaders() throws IOException {
          try {
              // ignore trailer headers
              while (true) {
                  String line = HttpParser.readLine(in, elementCharset);
                  if ((line == null) || (line.length() < 1)) {
                      break;
                  }
              }
          } catch(HttpException e) {
              LOG.error("Error parsing trailer headers", e);
              IOException ioe = new IOException(e.getMessage());
              ExceptionUtil.initCause(ioe, e); 
              throw ioe;
          }        
      }
  
      /**
       * Upon close, this reads the remainder of the chunked message,
       * leaving the underlying socket at a position to start reading the
       * next response without scanning.
       * @throws IOException If an IO problem occurs.
       */
      public void close() throws IOException {
          if (!closed) {
              try {
                  if (!eof) {
                      exhaustInputStream(this);
                  }
              } finally {
                  eof = true;
                  closed = true;
              }
          }
      }
  
      /**
       * Exhaust an input stream, reading until EOF has been encountered.
       *
       * <p>Note that this function is intended as a non-public utility.
       * This is a little weird, but it seemed silly to make a utility
       * class for this one function, so instead it is just static and
       * shared that way.</p>
       *
       * @param inStream The {@link InputStream} to exhaust.
       * @throws IOException If an IO problem occurs
       */
      static void exhaustInputStream(InputStream inStream) throws IOException {
          // read and discard the remainder of the message
          byte buffer[] = new byte[1024];
          while (inStream.read(buffer) >= 0) {
              ;
          }
      }
  }
  
  
  
  1.15      +110 -22   jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java
  
  Index: Part.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/Part.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- Part.java	18 Apr 2004 23:51:37 -0000	1.14
  +++ Part.java	6 Oct 2004 03:39:59 -0000	1.15
  @@ -53,13 +53,23 @@
       /** Log object for this class. */
       private static final Log LOG = LogFactory.getLog(Part.class);
   
  -    //TODO: Make this configurable
  -    
  -    /** The boundary */
  +    /** 
  +     * The boundary 
  +     * @deprecated use {@link org.apache.commons.httpclient.params.HttpMethodParams#MULTIPART_BOUNDARY}
  +     */
       protected static final String BOUNDARY = "----------------314159265358979323846";
       
  -    /** The boundary as a byte array */
  +    /** 
  +     * The boundary as a byte array.
  +     * @deprecated
  +     */
       protected static final byte[] BOUNDARY_BYTES = EncodingUtil.getAsciiBytes(BOUNDARY);
  +
  +    /**
  +     * The default boundary to be used if {@link #setBoundaryBytes(byte[])) has not
  +     * been called.
  +     */
  +    private static final byte[] DEFAULT_BOUNDARY_BYTES = BOUNDARY_BYTES;    
       
       /** Carriage return/linefeed */
       protected static final String CRLF = "\r\n";
  @@ -112,10 +122,16 @@
       /**
        * Return the boundary string.
        * @return the boundary string
  +     * @deprecated 
        */
       public static String getBoundary() {
           return BOUNDARY;
       }
  +
  +    /**
  +     * The ASCII bytes to use as the multipart boundary.
  +     */
  +    private byte[] boundaryBytes;
       
       /**
        * Return the name of this part.
  @@ -143,6 +159,41 @@
       public abstract String getTransferEncoding();
   
       /**
  +     * Gets the part boundary to be used.
  +     * @return
  +     * @since 3.0
  +     */
  +    protected byte[] getPartBoundary() {
  +        if (boundaryBytes == null) {
  +            // custom boundary bytes have not been set, use the default.
  +            return DEFAULT_BOUNDARY_BYTES;
  +        } else {
  +            return boundaryBytes;            
  +        }
  +    }
  +    
  +    /**
  +     * Sets the part boundary.  Only meant to be used by 
  +     * {@link Part#sendParts(OutputStream, Part[], byte[])}
  +     * and {@link Part#getLengthOfParts(Part[], byte[])}
  +     * @param boundaryBytes An array of ASCII bytes.
  +     * @since 3.0
  +     */
  +    void setPartBoundary(byte[] boundaryBytes) {
  +        this.boundaryBytes = boundaryBytes;
  +    }
  +    
  +    /**
  +     * Tests if this part can be sent more than once.
  +     * @return <code>true</code> if {@link #sendData(OutputStream)} can be successfully called 
  +     * more than once.
  +     * @since 3.0
  +     */
  +    public boolean isRepeatable() {
  +        return true;
  +    }
  +    
  +    /**
        * Write the start to the specified output stream
        * @param out The output stream
        * @throws IOException If an IO problem occurs.
  @@ -150,7 +201,7 @@
       protected void sendStart(OutputStream out) throws IOException {
           LOG.trace("enter sendStart(OutputStream out)");
           out.write(EXTRA_BYTES);
  -        out.write(BOUNDARY_BYTES);
  +        out.write(getPartBoundary());
           out.write(CRLF_BYTES);
       }
       
  @@ -291,49 +342,86 @@
       }
   
       /**
  -     * Write all parts and the last boundary to the specified output stream
  +     * Write all parts and the last boundary to the specified output stream.
        * 
  -     * @param out The output stream
  -     * @param parts The array of parts to be sent
  +     * @param out The stream to write to.
  +     * @param parts The parts to write.
        * 
  -     * @throws IOException If an IO problem occurs.
  +     * @throws IOException If an I/O error occurs while writing the parts.
        */
       public static void sendParts(OutputStream out, final Part[] parts)
  -    throws IOException {
  -        LOG.trace("enter sendParts(OutputStream out, Parts[])");
  +        throws IOException {
  +        sendParts(out, parts, DEFAULT_BOUNDARY_BYTES);
  +    }
  +
  +    /**
  +     * Write all parts and the last boundary to the specified output stream.
  +     * 
  +     * @param out The stream to write to.
  +     * @param parts The parts to write.
  +     * @param partBoundary The ASCII bytes to use as the part boundary.
  +     * 
  +     * @throws IOException If an I/O error occurs while writing the parts.
  +     * 
  +     * @since 3.0
  +     */
  +    public static void sendParts(OutputStream out, Part[] parts, byte[] partBoundary)
  +        throws IOException {
  +        
           if (parts == null) {
               throw new IllegalArgumentException("Parts may not be null"); 
           }
  +        if (partBoundary == null || partBoundary.length == 0) {
  +            throw new IllegalArgumentException("partBoundary may not be empty");
  +        }
           for (int i = 0; i < parts.length; i++) {
  +            // set the part boundary before the part is sent
  +            parts[i].setPartBoundary(partBoundary);
               parts[i].send(out);
           }
           out.write(EXTRA_BYTES);
  -        out.write(BOUNDARY_BYTES);
  +        out.write(partBoundary);
           out.write(EXTRA_BYTES);
           out.write(CRLF_BYTES);
       }
  -
  +    
       /**
        * Return the total sum of all parts and that of the last boundary
        * 
  -     * @param parts The array of parts
  -     * 
  -     * @return the total length
  +     * @param parts The parts.
  +     * @return The total length
        * 
  -     * @throws IOException If an IO problem occurs.
  +     * @throws IOException If an I/O error occurs while writing the parts.
        */
  -    public static long getLengthOfParts(final Part[] parts)
  +    public static long getLengthOfParts(Part[] parts)
       throws IOException {
  +        return getLengthOfParts(parts, DEFAULT_BOUNDARY_BYTES);
  +    }
  +    
  +    /**
  +     * Gets the length of the multipart message including the given parts.
  +     * 
  +     * @param parts The parts.
  +     * @param partBoundary The ASCII bytes to use as the part boundary.
  +     * @return The total length
  +     * 
  +     * @throws IOException If an I/O error occurs while writing the parts.
  +     * 
  +     * @since 3.0
  +     */
  +    public static long getLengthOfParts(Part[] parts, byte[] partBoundary) throws IOException {
           LOG.trace("getLengthOfParts(Parts[])");
           if (parts == null) {
               throw new IllegalArgumentException("Parts may not be null"); 
           }
           long total = 0;
           for (int i = 0; i < parts.length; i++) {
  +            // set the part boundary before we calculate the part's length
  +            parts[i].setPartBoundary(partBoundary);
               total += parts[i].length();
           }
           total += EXTRA_BYTES.length;
  -        total += BOUNDARY_BYTES.length;
  +        total += partBoundary.length;
           total += EXTRA_BYTES.length;
           total += CRLF_BYTES.length;
           return total;
  
  
  
  1.1                  jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java
  
  Index: MultipartRequestEntity.java
  ===================================================================
  /*
   * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/multipart/MultipartRequestEntity.java,v 1.1 2004/10/06 03:39:59 mbecke Exp $
   * $Revision: 1.1 $
   * $Date: 2004/10/06 03:39:59 $
   *
   * ====================================================================
   *
   *  Copyright 2002-2004 The Apache Software Foundation
   *
   *  Licensed under the Apache License, Version 2.0 (the "License");
   *  you may not use this file except in compliance with the License.
   *  You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   *  Unless required by applicable law or agreed to in writing, software
   *  distributed under the License is distributed on an "AS IS" BASIS,
   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   *  See the License for the specific language governing permissions and
   *  limitations under the License.
   * ====================================================================
   *
   * 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/>.
   *
   */
  package org.apache.commons.httpclient.methods.multipart;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.util.Random;
  
  import org.apache.commons.httpclient.methods.RequestEntity;
  import org.apache.commons.httpclient.params.HttpMethodParams;
  import org.apache.commons.httpclient.util.EncodingUtil;
  import org.apache.commons.logging.Log;
  import org.apache.commons.logging.LogFactory;
  
  /**
   * Implements a request entity suitable for an HTTP multipart POST method.
   * <p>
   * The HTTP multipart POST method is defined in section 3.3 of
   * <a href="http://www.ietf.org/rfc/rfc1867.txt">RFC1867</a>:
   * <blockquote>
   * The media-type multipart/form-data follows the rules of all multipart
   * MIME data streams as outlined in RFC 1521. The multipart/form-data contains 
   * a series of parts. Each part is expected to contain a content-disposition 
   * header where the value is "form-data" and a name attribute specifies 
   * the field name within the form, e.g., 'content-disposition: form-data; 
   * name="xxxxx"', where xxxxx is the field name corresponding to that field.
   * Field names originally in non-ASCII character sets may be encoded using 
   * the method outlined in RFC 1522.
   * </blockquote>
   * </p>
   * <p>This entity is designed to be used in conjunction with the 
   * {@link org.apache.commons.httpclient.methods.PostMethod post method} to provide
   * multipart posts.  Example usage:</p>
   * <code>
   *  File f = new File("/path/fileToUpload.txt");
   *  PostMethod filePost = new PostMethod("http://host/some_path");
   *  Part[] parts = {
   *      new StringPart("param_name", "value"),
   *      new FilePart(f.getName(), f)
   *  };
   *  filePost.setRequestEntity(
   *      new MultipartRequestEntity(parts, filePost.getParams())
   *      );
   *  HttpClient client = new HttpClient();
   *  int status = client.executeMethod(filePost);
   * </code>
   * 
   * @since 3.0
   */
  public class MultipartRequestEntity implements RequestEntity {
  
      private static final Log log = LogFactory.getLog(MultipartRequestEntity.class);
      
      /** The Content-Type for multipart/form-data. */
      private static final String MULTIPART_FORM_CONTENT_TYPE = "multipart/form-data";
      
      /**
       * The pool of ASCII chars to be used for generating a multipart boundary.
       */
      private static byte[] MULTIPART_CHARS = EncodingUtil.getAsciiBytes(
          "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ");
      
      /**
       * Generates a random multipart boundary string.
       * @return
       */
      private static byte[] generateMultipartBoundary() {
          Random rand = new Random();
          byte[] bytes = new byte[rand.nextInt(11) + 30]; // a random size from 30 to 40
          for (int i = 0; i < bytes.length; i++) {
              bytes[i] = MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)];
          }
          return bytes;
      }
      
      private Part[] parts;
      
      private byte[] multipartBoundary;
      
      private HttpMethodParams params;
      
      /**
       * Creates a new multipart entity containing the given parts.
       * @param parts The parts to include.
       * @param params The params of the HttpMethod using this entity.
       */
      public MultipartRequestEntity(Part[] parts, HttpMethodParams params) {
          if (parts == null) {
              throw new IllegalArgumentException("parts cannot be null");
          }
          if (params == null) {
              throw new IllegalArgumentException("params cannot be null");
          }
          this.parts = parts;
          this.params = params;
      }
  
      private byte[] getMultipartBoundary() {
          if (multipartBoundary == null) {
              String temp = (String) params.getParameter(HttpMethodParams.MULTIPART_BOUNDARY);
              if (temp != null) {
                  multipartBoundary = EncodingUtil.getAsciiBytes(temp);
              } else {
                  multipartBoundary = generateMultipartBoundary();
              }
          }
          return multipartBoundary;
      }
  
      /**
       * Returns <code>true</code> if all parts are repeatable, <code>false</code> otherwise.
       * @see org.apache.commons.httpclient.methods.RequestEntity#isRepeatable()
       */
      public boolean isRepeatable() {
          for (int i = 0; i < parts.length; i++) {
              if (!parts[i].isRepeatable()) {
                  return false;
              }
          }
          return true;
      }
  
      /* (non-Javadoc)
       * @see org.apache.commons.httpclient.methods.RequestEntity#writeRequest(java.io.OutputStream)
       */
      public void writeRequest(OutputStream out) throws IOException {
          Part.sendParts(out, parts, getMultipartBoundary());
      }
  
      /* (non-Javadoc)
       * @see org.apache.commons.httpclient.methods.RequestEntity#getContentLength()
       */
      public long getContentLength() {
          try {
              return Part.getLengthOfParts(parts, getMultipartBoundary());            
          } catch (Exception e) {
              log.error("An exception occurred while getting the length of the parts", e);
              return 0;
          }
      }
  
      /* (non-Javadoc)
       * @see org.apache.commons.httpclient.methods.RequestEntity#getContentType()
       */
      public String getContentType() {
          StringBuffer buffer = new StringBuffer(MULTIPART_FORM_CONTENT_TYPE);
          buffer.append("; boundary=");
          buffer.append(EncodingUtil.getAsciiString(getMultipartBoundary()));
          return buffer.toString();
      }
  
  }
  
  
  
  1.4       +14 -1     jakarta-commons/httpclient/xdocs/preference-api.xml
  
  Index: preference-api.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/xdocs/preference-api.xml,v
  retrieving revision 1.3
  retrieving revision 1.4
  diff -u -r1.3 -r1.4
  --- preference-api.xml	22 Sep 2004 17:23:33 -0000	1.3
  +++ preference-api.xml	6 Oct 2004 03:39:59 -0000	1.4
  @@ -393,6 +393,19 @@
               <p><code>&lt;undefined&gt;</code></p>
             </td>
           </tr>
  +        <tr>
  +          <td><p>http.method.multipart.boundary</p></td>
  +          <td><p>String</p></td>
  +          <td><p>The multipart boundary string to use in conjunction with the 
  +                 <a href="apidocs/org/apache/commons/httpclient/params/MultipartRequestEntity.html">
  +                 MultipartRequestEntity</a>.
  +                 When not set a random value will be generated for each request.
  +              </p>
  +          </td>
  +          <td>
  +            <p><code>&lt;undefined&gt;</code></p>
  +          </td>
  +        </tr>
         </table>
          <p>
            Whenever a parameter is left undefined (no value is explicitly set anywhere in 
  
  
  
  1.16      +13 -4     jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpMethodParams.java
  
  Index: HttpMethodParams.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/params/HttpMethodParams.java,v
  retrieving revision 1.15
  retrieving revision 1.16
  diff -u -r1.15 -r1.16
  --- HttpMethodParams.java	17 Sep 2004 08:00:51 -0000	1.15
  +++ HttpMethodParams.java	6 Oct 2004 03:39:59 -0000	1.16
  @@ -258,6 +258,15 @@
       public static final String BUFFER_WARN_TRIGGER_LIMIT = "http.method.response.buffer.warnlimit";
       
       /**
  +     * Sets the value to use as the multipart boundary.
  +     * <p>
  +     * This parameter expects a value if type {@link String}.
  +     * </p>
  +     * @see org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity
  +     */
  +    public static final String MULTIPART_BOUNDARY = "http.method.multipart.boundary";
  +    
  +    /**
        * Creates a new collection of parameters with the collection returned
        * by {@link #getDefaultParams()} as a parent. The collection will defer
        * to its parent for a default value if a particular parameter is not 
  
  
  
  1.10      +12 -5     jakarta-commons/httpclient/src/examples/MultipartFileUploadApp.java
  
  Index: MultipartFileUploadApp.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/examples/MultipartFileUploadApp.java,v
  retrieving revision 1.9
  retrieving revision 1.10
  diff -u -r1.9 -r1.10
  --- MultipartFileUploadApp.java	22 Feb 2004 18:08:45 -0000	1.9
  +++ MultipartFileUploadApp.java	6 Oct 2004 03:39:59 -0000	1.10
  @@ -39,8 +39,8 @@
   
   import javax.swing.DefaultComboBoxModel;
   import javax.swing.JButton;
  -import javax.swing.JComboBox;
   import javax.swing.JCheckBox;
  +import javax.swing.JComboBox;
   import javax.swing.JFileChooser;
   import javax.swing.JFrame;
   import javax.swing.JLabel;
  @@ -50,7 +50,10 @@
   
   import org.apache.commons.httpclient.HttpClient;
   import org.apache.commons.httpclient.HttpStatus;
  -import org.apache.commons.httpclient.methods.MultipartPostMethod;
  +import org.apache.commons.httpclient.methods.PostMethod;
  +import org.apache.commons.httpclient.methods.multipart.FilePart;
  +import org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity;
  +import org.apache.commons.httpclient.methods.multipart.Part;
   import org.apache.commons.httpclient.params.HttpMethodParams;
   
   /**
  @@ -147,14 +150,18 @@
                           cmbURLModel.addElement(targetURL);
                       }
   
  -                    MultipartPostMethod filePost =
  -                        new MultipartPostMethod(targetURL);
  +                    PostMethod filePost = new PostMethod(targetURL);
   
                       filePost.getParams().setBooleanParameter(HttpMethodParams.USE_EXPECT_CONTINUE,
                       cbxExpectHeader.isSelected());
                       try {
                           appendMessage("Uploading " + targetFile.getName() + " to " + targetURL);
  -                        filePost.addParameter(targetFile.getName(), targetFile);
  +                        Part[] parts = {
  +                            new FilePart(targetFile.getName(), targetFile)
  +                        };
  +                        filePost.setRequestEntity(
  +                            new MultipartRequestEntity(parts, filePost.getParams())
  +                            );
                           HttpClient client = new HttpClient();
                           client.getHttpConnectionManager().
                               getParams().setConnectionTimeout(5000);
  
  
  
  1.27      +6 -3      jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java
  
  Index: MultipartPostMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/MultipartPostMethod.java,v
  retrieving revision 1.26
  retrieving revision 1.27
  diff -u -r1.26 -r1.27
  --- MultipartPostMethod.java	13 Jun 2004 20:22:19 -0000	1.26
  +++ MultipartPostMethod.java	6 Oct 2004 03:39:59 -0000	1.27
  @@ -71,6 +71,9 @@
    * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
    *
    * @since 2.0
  + * 
  + * @deprecated Use {@link org.apache.commons.httpclient.methods.multipart.MultipartRequestEntity}
  + * in conjunction with {@link org.apache.commons.httpclient.methods.PostMethod} instead.
    */
   public class MultipartPostMethod extends ExpectContinueMethod {
   
  
  
  
  1.11      +56 -7     jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java
  
  Index: SimpleHttpServerConnection.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleHttpServerConnection.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- SimpleHttpServerConnection.java	14 Sep 2004 15:50:41 -0000	1.10
  +++ SimpleHttpServerConnection.java	6 Oct 2004 03:39:59 -0000	1.11
  @@ -31,15 +31,20 @@
   
   package org.apache.commons.httpclient.server;
   
  +import java.io.ByteArrayOutputStream;
   import java.io.IOException;
   import java.io.InputStream;
   import java.io.OutputStream;
   import java.io.UnsupportedEncodingException;
   import java.net.Socket;
   
  +import org.apache.commons.httpclient.ContentLengthInputStream;
  +import org.apache.commons.httpclient.Header;
  +import org.apache.commons.httpclient.HeaderGroup;
   import org.apache.commons.httpclient.HttpException;
   import org.apache.commons.httpclient.HttpParser;
   import org.apache.commons.httpclient.HttpStatus;
  +import org.apache.commons.httpclient.SimpleChunkedInputStream;
   import org.apache.commons.logging.Log;
   import org.apache.commons.logging.LogFactory;
   
  @@ -52,6 +57,7 @@
   
       private static final Log LOG = LogFactory.getLog(SimpleHttpServerConnection.class);
       private static final String HTTP_ELEMENT_CHARSET = "US-ASCII";
  +    public static final String DEFAULT_CONTENT_CHARSET = "ISO-8859-1";
   
       private SimpleHttpServer server;
       private Socket socket;
  @@ -65,6 +71,7 @@
       public SimpleHttpServerConnection(SimpleHttpServer server, Socket socket) throws IOException {
           this.server = server;
           this.socket = socket;
  +        this.socket.setSoTimeout(2000);
           this.in = socket.getInputStream();
           this.out = socket.getOutputStream();
       }
  @@ -152,10 +159,9 @@
   
           SimpleRequest request = null;
           try {
  -            request = new SimpleRequest( 
  -                RequestLine.parseLine(line),
  -                HttpParser.parseHeaders(in, HTTP_ELEMENT_CHARSET),
  -                null);
  +            RequestLine statusLine = RequestLine.parseLine(line);
  +            Header[] headers = HttpParser.parseHeaders(in, HTTP_ELEMENT_CHARSET);
  +            request = new SimpleRequest(statusLine, headers, getBody(statusLine, headers, in));
           } catch (HttpException e) {
               connectionClose();
               SimpleResponse response = ErrorResponse.getInstance().
  @@ -169,6 +175,49 @@
           }
           server.processRequest(this, request);
           out.flush();
  +    }
  +    
  +    /**
  +     * Reads the request body into a byte[].
  +     * @param statusLine The request status line.
  +     * @param headers The request headers.
  +     * @param is The request input stream.
  +     * @return The content as an arra of bytes.
  +     * @throws IOException
  +     */
  +    private byte[] getBody(RequestLine statusLine, Header[] headers, InputStream is) throws IOException {
  +
  +        // only PUT and POST have content
  +        if (
  +            !statusLine.getMethod().equalsIgnoreCase("POST")
  +            && !statusLine.getMethod().equalsIgnoreCase("PUT")
  +            ) {
  +            return new byte[0];
  +        }
  +        
  +        HeaderGroup headerGroup = new HeaderGroup();
  +        headerGroup.setHeaders(headers);
  +        Header contentLength = headerGroup.getFirstHeader("Content-Length");
  +        InputStream contentStream = is;
  +        int length = -1;
  +        if (contentLength != null) {
  +            length = Integer.parseInt(contentLength.getValue().trim());
  +            contentStream = new ContentLengthInputStream(is, (long) length);
  +        } else {
  +            Header transferEncoding = headerGroup.getFirstHeader("Transfer-Encoding");
  +            if (transferEncoding != null && transferEncoding.getValue().indexOf("chunked") != -1) {
  +                contentStream = new SimpleChunkedInputStream(is, DEFAULT_CONTENT_CHARSET);
  +            }
  +        }
  +        
  +        byte[] buff = new byte[4096];
  +        int bytesRead = 0;
  +        ByteArrayOutputStream os = new ByteArrayOutputStream(Math.max(length, 4096));
  +        while ((bytesRead = contentStream.read(buff)) != -1) {
  +            os.write(buff, 0, bytesRead);
  +        }
  +        
  +        return os.toByteArray();
       }
   
       public InputStream getInputStream() {
  
  
  
  1.2       +18 -10    jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleRequest.java
  
  Index: SimpleRequest.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/server/SimpleRequest.java,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- SimpleRequest.java	27 Feb 2004 19:06:19 -0000	1.1
  +++ SimpleRequest.java	6 Oct 2004 03:39:59 -0000	1.2
  @@ -31,6 +31,7 @@
   
   package org.apache.commons.httpclient.server;
   
  +import java.io.IOException;
   import java.util.Iterator;
   
   import org.apache.commons.httpclient.Header;
  @@ -48,7 +49,7 @@
       private RequestLine requestLine = null;
       private String contentType = "text/plain";
       private String charSet = null;
  -    private String bodyString = null;
  +    private byte[] body = null;
       private HeaderGroup headers = new HeaderGroup();
   
       public SimpleRequest() {
  @@ -58,13 +59,14 @@
       public SimpleRequest(
           final RequestLine requestLine,
           final Header[] headers,
  -        final String bodyString)
  +        final byte[] body)
       {
           super();
           if (requestLine == null) {
               throw new IllegalArgumentException("Request line may not be null");
           }
           this.requestLine = requestLine;
  +        this.body = body;
           if (headers != null) {
               this.headers.setHeaders(headers);
               Header content = this.headers.getFirstHeader("Content-Type");
  @@ -79,9 +81,6 @@
                   }
               }
           }
  -        this.bodyString = bodyString;
  -        
  -        
       }
   
       public String getContentType() {
  @@ -92,8 +91,17 @@
           return this.charSet;
       }
       
  -    public String getBodyString() {
  -        return this.bodyString;
  +    public byte[] getBody() {
  +        return body;
  +    }
  +    
  +    public String getBodyString() throws IOException {
  +        return new String(
  +            body, 
  +            charSet == null 
  +            ? SimpleHttpServerConnection.DEFAULT_CONTENT_CHARSET 
  +            : charSet
  +            );
       }
   
       public RequestLine getRequestLine() {
  
  
  

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


Mime
View raw message