commons-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ol...@apache.org
Subject cvs commit: jakarta-commons/httpclient/src/test/org/apache/commons/httpclient TestStreams.java
Date Thu, 11 Mar 2004 20:55:27 GMT
olegk       2004/03/11 12:55:27

  Modified:    httpclient/src/java/org/apache/commons/httpclient
                        ChunkedOutputStream.java
               httpclient/src/java/org/apache/commons/httpclient/methods
                        EntityEnclosingMethod.java
               httpclient/src/test/org/apache/commons/httpclient
                        TestStreams.java
  Log:
  A better implementation of ChunkedOutputStream:
  
  * Writes are buffered to an internal buffer (2048 default size)
  * Chunks are guaranteed to be at least as large as the buffer size (except for the last
chunk)
  
  Contributed by Mohammad Rezaei <mohammad.rezaei at gs.com>, Goldman, Sachs & Co.
  Reviewed by Ortwin Glueck, Michael Becke, Oleg Kalnichevski
  
  Revision  Changes    Path
  1.14      +89 -142   jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedOutputStream.java
  
  Index: ChunkedOutputStream.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ChunkedOutputStream.java,v
  retrieving revision 1.13
  retrieving revision 1.14
  diff -u -r1.13 -r1.14
  --- ChunkedOutputStream.java	22 Feb 2004 18:08:45 -0000	1.13
  +++ ChunkedOutputStream.java	11 Mar 2004 20:55:27 -0000	1.14
  @@ -28,37 +28,22 @@
    * [Additional notices, if required by prior licensing conditions]
    *
    */
  -
   package org.apache.commons.httpclient;
   
   import java.io.IOException;
   import java.io.OutputStream;
   
   import org.apache.commons.httpclient.util.EncodingUtil;
  -import org.apache.commons.logging.Log;
  -import org.apache.commons.logging.LogFactory;
   
   /**
  - * <p>
  - * Wrapper supporting the chunked transfer encoding.
  - * </p>
  - *
  - * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
  - * @author Sean C. Sullivan
  - * @author <a href="mailto:dion@apache.org">dIon Gillard</a>
  - * @author <a href="mailto:oleg@ural.ru">Oleg Kalnichevski</a>
  - * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a>
  - * @version $Revision$ $Date$
  - *
  - * @see ChunkedInputStream
  - * @since 2.0
  + * Implements HTTP chunking support. Writes are buffered to an internal buffer (2048 default
size).
  + * Chunks are guaranteed to be at least as large as the buffer size (except for the last
chunk).
    *
  + * @author Mohammad Rezaei, Goldman, Sachs & Co.
    */
   public class ChunkedOutputStream extends OutputStream {
   
       // ------------------------------------------------------- Static Variables
  -
  -    /** <tt>"\r\n"</tt>, as bytes. */
       private static final byte CRLF[] = new byte[] {(byte) 13, (byte) 10};
   
       /** End chunk */
  @@ -67,170 +52,132 @@
       /** 0 */
       private static final byte ZERO[] = new byte[] {(byte) '0'};
   
  -    /** 1 */
  -    private static final byte ONE[] = new byte[] {(byte) '1'};
  -
  -    /** Log object for this class. */
  -    private static final Log LOG = LogFactory.getLog(ChunkedOutputStream.class);
  -
       // ----------------------------------------------------- Instance Variables
  +    private OutputStream stream = null;
   
  -    /** Has this stream been closed? */
  -    private boolean closed = false;
  +    private byte[] cache;
   
  -    /** The underlying output stream to which we will write data */
  -    private OutputStream stream = null;
  +    private int cachePosition = 0;
   
  -    // ----------------------------------------------------------- Constructors
  +    private boolean wroteLastChunk = false;
   
  +    // ----------------------------------------------------------- Constructors
       /**
  -     * Construct an output stream wrapping the given stream.
  -     * The stream will not use chunking.
  -     *
  -     * @param stream wrapped output stream. Must be non-null.
  -     */
  -    public ChunkedOutputStream(OutputStream stream) {
  -        if (stream == null) {
  -            throw new IllegalArgumentException("Stream parameter may not be null");
  -        }
  +     * Wraps a stream and chunks the output.
  +     * @param stream to wrap
  +     * @param bufferSize minimum chunk size (excluding last chunk)
  +     * @throws IOException
  +     */
  +    public ChunkedOutputStream(OutputStream stream, int bufferSize) throws IOException
{
  +        this.cache = new byte[bufferSize];
           this.stream = stream;
       }
   
  -
  -    // --------------------------------------------------------- Public Methods
  -
       /**
  -     * Writes a <code>String</code> to the client, without a carriage return
  -     * line feed (CRLF) character at the end. The platform default encoding is
  -     * used!
  -     *
  -     * @param s the <code>String</code> to send to the client. Must be non-null.
  -     * @throws IOException if an input or output exception occurred
  -     */
  -    public void print(String s) throws IOException {
  -        LOG.trace("enter ChunckedOutputStream.print(String)");
  -        if (s == null) {
  -            s = "null";
  -        }
  -        write(s.getBytes());
  +     * Wraps a stream and chunks the output. The default buffer size of 2048 was chosen
because
  +     * the chunk overhead is less than 0.5%
  +     * @param stream
  +     * @throws IOException
  +     */
  +    public ChunkedOutputStream(OutputStream stream) throws IOException {
  +        this(stream, 2048);
       }
   
  +    // ----------------------------------------------------------- Internal methods
       /**
  -     * Writes a carriage return-line feed (CRLF) to the client.
  -     *
  -     * @throws IOException   if an input or output exception occurred
  +     * Writes the cache out onto the underlying stream
  +     * @throws IOException
        */
  -    public void println() throws IOException {
  -        print("\r\n");
  +    protected void flushCache() throws IOException {
  +        if (cachePosition > 0) {
  +            byte chunkHeader[] = EncodingUtil.getAsciiBytes(
  +                    Integer.toHexString(cachePosition) + "\r\n");
  +            stream.write(chunkHeader, 0, chunkHeader.length);
  +            stream.write(cache, 0, cachePosition);
  +            stream.write(ENDCHUNK, 0, ENDCHUNK.length);
  +            cachePosition = 0;
  +        }
       }
   
       /**
  -     * Writes a <code>String</code> to the client,
  -     * followed by a carriage return-line feed (CRLF).
  -     *
  -     * @param s         the </code>String</code> to write to the client
  -     * @exception IOException   if an input or output exception occurred
  +     * Writes the cache and bufferToAppend to the underlying stream
  +     * as one large chunk
  +     * @param bufferToAppend
  +     * @param off
  +     * @param len
  +     * @throws IOException
        */
  -    public void println(String s) throws IOException {
  -        print(s);
  -        println();
  +    protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws
IOException {
  +        byte chunkHeader[] = EncodingUtil.getAsciiBytes(
  +                Integer.toHexString(cachePosition + len) + "\r\n");
  +        stream.write(chunkHeader, 0, chunkHeader.length);
  +        stream.write(cache, 0, cachePosition);
  +        stream.write(bufferToAppend, off, len);
  +        stream.write(ENDCHUNK, 0, ENDCHUNK.length);
  +        cachePosition = 0;
       }
   
  -    // -------------------------------------------- OutputStream Methods
  +    protected void writeClosingChunk() throws IOException {
  +        // Write the final chunk.
   
  -    /**
  -     * Write the specified byte to our output stream.
  -     *
  -     * @param b The byte to be written
  -     * @throws IOException if an input/output error occurs
  -     * @throws IllegalStateException if stream already closed
  -     */
  -    public void write (int b) throws IOException, IllegalStateException {
  -        if (closed) {
  -            throw new IllegalStateException("Output stream already closed"); 
  -        }
  -        //FIXME: If using chunking, the chunks are ONE byte long!
  -        stream.write(ONE, 0, ONE.length);
  +        stream.write(ZERO, 0, ZERO.length);
           stream.write(CRLF, 0, CRLF.length);
  -        stream.write(b);
           stream.write(ENDCHUNK, 0, ENDCHUNK.length);
  -        LOG.debug("Writing chunk (length: 1)");
       }
   
  +    // ----------------------------------------------------------- Public Methods
       /**
  -     * Write the specified byte array.
  -     *
  -     * @param b the byte array to write out
  -     * @param off the offset within <code>b</code> to start writing from
  -     * @param len the length of data within <code>b</code> to write
  -     * @throws IOException when errors occur writing output
  +     * Must be called to ensure the internal cache is flushed and the closing chunk is
written.
  +     * @throws IOException
        */
  -    public void write (byte[] b, int off, int len) throws IOException {
  -        LOG.trace("enter ChunckedOutputStream.write(byte[], int, int)");
  -
  -        if (closed) {
  -            throw new IllegalStateException("Output stream already closed"); 
  -        }
  -        byte chunkHeader[] = EncodingUtil.getAsciiBytes (
  -            Integer.toHexString(len) + "\r\n");
  -        stream.write(chunkHeader, 0, chunkHeader.length);
  -        stream.write(b, off, len);
  -        stream.write(ENDCHUNK, 0, ENDCHUNK.length);
  -        if (LOG.isDebugEnabled()) {
  -            LOG.debug("Writing chunk (length: " + len + ")");
  +    public void finish() throws IOException {
  +        if (!wroteLastChunk) {
  +            flushCache();
  +            writeClosingChunk();
  +            wroteLastChunk = true;
           }
       }
   
  +    // -------------------------------------------- OutputStream Methods
  +    public void write(int b) throws IOException {
  +        cache[cachePosition] = (byte) b;
  +        cachePosition++;
  +        if (cachePosition == cache.length) flushCache();
  +    }
  +
       /**
  -     * Close this output stream, causing any buffered data to be flushed and
  -     * any further output data to throw an IOException. The underlying stream
  -     * is not closed!
  -     *
  -     * @throws IOException if an error occurs closing the stream
  -     */
  -    public void writeClosingChunk() throws IOException {
  -        LOG.trace("enter ChunkedOutputStream.writeClosingChunk()");
  -
  -        if (!closed) {
  -            try {
  -                // Write the final chunk.
  -                stream.write(ZERO, 0, ZERO.length);
  -                stream.write(CRLF, 0, CRLF.length);
  -                stream.write(ENDCHUNK, 0, ENDCHUNK.length);
  -                LOG.debug("Writing closing chunk");
  -            } catch (IOException e) {
  -                LOG.debug("Unexpected exception caught when closing "
  -                    + "output stream", e);
  -                throw e;
  -            } finally {
  -                // regardless of what happens, mark the stream as closed.
  -                // if there are errors closing it, there's not much we can do
  -                // about it
  -                closed = true;
  -            }
  +     * Writes the array. If the array does not fit within the buffer, it is
  +     * not split, but rather written out as one large chunk.
  +     * @param b
  +     * @throws IOException
  +     */
  +    public void write(byte b[]) throws IOException {
  +        this.write(b, 0, b.length);
  +    }
  +
  +    public void write(byte src[], int off, int len) throws IOException {
  +        if (len >= cache.length - cachePosition) {
  +            flushCacheWithAppend(src, off, len);
  +        } else {
  +            System.arraycopy(src, off, cache, cachePosition, len);
  +            cachePosition += len;
           }
       }
   
       /**
  -     * Flushes the underlying stream.
  -     * @throws IOException If an IO problem occurs.
  +     * Flushes the underlying stream, but leaves the internal buffer alone.
  +     * @throws IOException
        */
       public void flush() throws IOException {
           stream.flush();
       }
   
       /**
  -     * Close this output stream, causing any buffered data to be flushed and
  -     * any further output data to throw an IOException. The underlying stream
  -     * is not closed!
  -     *
  -     * @throws IOException if an error occurs closing the stream
  +     * Finishes writing to the underlying stream, but does NOT close the underlying stream.
  +     * @throws IOException
        */
       public void close() throws IOException {
  -        LOG.trace("enter ChunkedOutputStream.close()");
  -        writeClosingChunk();
  +        finish();
           super.close();
       }
  -
  -
   }
  
  
  
  1.30      +5 -5      jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java
  
  Index: EntityEnclosingMethod.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/methods/EntityEnclosingMethod.java,v
  retrieving revision 1.29
  retrieving revision 1.30
  diff -u -r1.29 -r1.30
  --- EntityEnclosingMethod.java	22 Feb 2004 18:08:48 -0000	1.29
  +++ EntityEnclosingMethod.java	11 Mar 2004 20:55:27 -0000	1.30
  @@ -490,7 +490,7 @@
           }
           // This is hardly the most elegant solution to closing chunked stream
           if (outstream instanceof ChunkedOutputStream) {
  -            ((ChunkedOutputStream) outstream).writeClosingChunk();
  +            ((ChunkedOutputStream) outstream).finish();
           }
           if ((contentLength > 0) && (total < contentLength)) {
               throw new IOException("Unexpected end of input stream after "
  
  
  
  1.15      +86 -3     jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestStreams.java
  
  Index: TestStreams.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons/httpclient/src/test/org/apache/commons/httpclient/TestStreams.java,v
  retrieving revision 1.14
  retrieving revision 1.15
  diff -u -r1.14 -r1.15
  --- TestStreams.java	22 Feb 2004 18:08:49 -0000	1.14
  +++ TestStreams.java	11 Mar 2004 20:55:27 -0000	1.15
  @@ -159,6 +159,89 @@
           assertEquals(input, output);
       }
   
  +    public void testChunkedOutputStream() throws IOException {
  +        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  +        ChunkedOutputStream out = new ChunkedOutputStream(buffer, 2);
  +        out.write('1');  
  +        out.write('2');  
  +        out.write('3');  
  +        out.write('4');  
  +        out.finish();
  +        out.close();
  +        
  +        byte [] rawdata =  buffer.toByteArray();
  +        
  +        assertEquals(19, rawdata.length);
  +        assertEquals('2', rawdata[0]);
  +        assertEquals('\r', rawdata[1]);
  +        assertEquals('\n', rawdata[2]);
  +        assertEquals('1', rawdata[3]);
  +        assertEquals('2', rawdata[4]);
  +        assertEquals('\r', rawdata[5]);
  +        assertEquals('\n', rawdata[6]);
  +        assertEquals('2', rawdata[7]);
  +        assertEquals('\r', rawdata[8]);
  +        assertEquals('\n', rawdata[9]);
  +        assertEquals('3', rawdata[10]);
  +        assertEquals('4', rawdata[11]);
  +        assertEquals('\r', rawdata[12]);
  +        assertEquals('\n', rawdata[13]);
  +        assertEquals('0', rawdata[14]);
  +        assertEquals('\r', rawdata[15]);
  +        assertEquals('\n', rawdata[16]);
  +        assertEquals('\r', rawdata[17]);
  +        assertEquals('\n', rawdata[18]);
  +    }
  +
  +    public void testChunkedOutputStreamLargeChunk() throws IOException {
  +        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  +        ChunkedOutputStream out = new ChunkedOutputStream(buffer, 2);
  +        out.write(new byte[] {'1', '2', '3', '4'});
  +        out.finish();
  +        out.close();
  +        
  +        byte [] rawdata =  buffer.toByteArray();
  +        
  +        assertEquals(14, rawdata.length);
  +        assertEquals('4', rawdata[0]);
  +        assertEquals('\r', rawdata[1]);
  +        assertEquals('\n', rawdata[2]);
  +        assertEquals('1', rawdata[3]);
  +        assertEquals('2', rawdata[4]);
  +        assertEquals('3', rawdata[5]);
  +        assertEquals('4', rawdata[6]);
  +        assertEquals('\r', rawdata[7]);
  +        assertEquals('\n', rawdata[8]);
  +        assertEquals('0', rawdata[9]);
  +        assertEquals('\r', rawdata[10]);
  +        assertEquals('\n', rawdata[11]);
  +        assertEquals('\r', rawdata[12]);
  +        assertEquals('\n', rawdata[13]);
  +    }
  +
  +    public void testChunkedOutputStreamSmallChunk() throws IOException {
  +        ByteArrayOutputStream buffer = new ByteArrayOutputStream();
  +        ChunkedOutputStream out = new ChunkedOutputStream(buffer, 2);
  +        out.write('1');  
  +        out.finish();
  +        out.close();
  +        
  +        byte [] rawdata =  buffer.toByteArray();
  +        
  +        assertEquals(11, rawdata.length);
  +        assertEquals('1', rawdata[0]);
  +        assertEquals('\r', rawdata[1]);
  +        assertEquals('\n', rawdata[2]);
  +        assertEquals('1', rawdata[3]);
  +        assertEquals('\r', rawdata[4]);
  +        assertEquals('\n', rawdata[5]);
  +        assertEquals('0', rawdata[6]);
  +        assertEquals('\r', rawdata[7]);
  +        assertEquals('\n', rawdata[8]);
  +        assertEquals('\r', rawdata[9]);
  +        assertEquals('\n', rawdata[10]);
  +    }
  +
       // ------------------------------------------------------- TestCase Methods
   
       public static Test suite() {
  
  
  

---------------------------------------------------------------------
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