/ org.apache.commons.httpclient / src / org / apache / commons / httpclient / ChunkedOutputStream.java
ChunkedOutputStream.java
  1  /*
  2   * $Header: /home/jerenkrantz/tmp/commons/commons-convert/cvs/home/cvs/jakarta-commons//httpclient/src/java/org/apache/commons/httpclient/ChunkedOutputStream.java,v 1.16 2004/05/13 04:03:25 mbecke Exp $
  3   * $Revision: 480424 $
  4   * $Date: 2006-11-29 06:56:49 +0100 (Wed, 29 Nov 2006) $
  5   *
  6   * ====================================================================
  7   *
  8   *  Licensed to the Apache Software Foundation (ASF) under one or more
  9   *  contributor license agreements.  See the NOTICE file distributed with
 10   *  this work for additional information regarding copyright ownership.
 11   *  The ASF licenses this file to You under the Apache License, Version 2.0
 12   *  (the "License"); you may not use this file except in compliance with
 13   *  the License.  You may obtain a copy of the License at
 14   *
 15   *      http://www.apache.org/licenses/LICENSE-2.0
 16   *
 17   *  Unless required by applicable law or agreed to in writing, software
 18   *  distributed under the License is distributed on an "AS IS" BASIS,
 19   *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 20   *  See the License for the specific language governing permissions and
 21   *  limitations under the License.
 22   * ====================================================================
 23   *
 24   * This software consists of voluntary contributions made by many
 25   * individuals on behalf of the Apache Software Foundation.  For more
 26   * information on the Apache Software Foundation, please see
 27   * <http://www.apache.org/>.
 28   *
 29   */
 30  
 31  package org.apache.commons.httpclient;
 32  
 33  import java.io.IOException;
 34  import java.io.OutputStream;
 35  
 36  import org.apache.commons.httpclient.util.EncodingUtil;
 37  
 38  /**
 39   * Implements HTTP chunking support. Writes are buffered to an internal buffer (2048 default size).
 40   * Chunks are guaranteed to be at least as large as the buffer size (except for the last chunk).
 41   *
 42   * @author Mohammad Rezaei, Goldman, Sachs & Co.
 43   */
 44  public class ChunkedOutputStream extends OutputStream {
 45  
 46      // ------------------------------------------------------- Static Variables
 47      private static final byte CRLF[] = new byte[] {(byte) 13, (byte) 10};
 48  
 49      /** End chunk */
 50      private static final byte ENDCHUNK[] = CRLF;
 51  
 52      /** 0 */
 53      private static final byte ZERO[] = new byte[] {(byte) '0'};
 54  
 55      // ----------------------------------------------------- Instance Variables
 56      private OutputStream stream = null;
 57  
 58      private byte[] cache;
 59  
 60      private int cachePosition = 0;
 61  
 62      private boolean wroteLastChunk = false;
 63  
 64      // ----------------------------------------------------------- Constructors
 65      /**
 66       * Wraps a stream and chunks the output.
 67       * @param stream to wrap
 68       * @param bufferSize minimum chunk size (excluding last chunk)
 69       * @throws IOException
 70       * 
 71       * @since 3.0
 72       */
 73      public ChunkedOutputStream(OutputStream stream, int bufferSize) throws IOException {
 74          this.cache = new byte[bufferSize];
 75          this.stream = stream;
 76      }
 77  
 78      /**
 79       * Wraps a stream and chunks the output. The default buffer size of 2048 was chosen because
 80       * the chunk overhead is less than 0.5%
 81       * @param stream
 82       * @throws IOException
 83       */
 84      public ChunkedOutputStream(OutputStream stream) throws IOException {
 85          this(stream, 2048);
 86      }
 87  
 88      // ----------------------------------------------------------- Internal methods
 89      /**
 90       * Writes the cache out onto the underlying stream
 91       * @throws IOException
 92       * 
 93       * @since 3.0
 94       */
 95      protected void flushCache() throws IOException {
 96          if (cachePosition > 0) {
 97              byte chunkHeader[] = EncodingUtil.getAsciiBytes(
 98                      Integer.toHexString(cachePosition) + "\r\n");
 99              stream.write(chunkHeader, 0, chunkHeader.length);
100              stream.write(cache, 0, cachePosition);
101              stream.write(ENDCHUNK, 0, ENDCHUNK.length);
102              cachePosition = 0;
103          }
104      }
105  
106      /**
107       * Writes the cache and bufferToAppend to the underlying stream
108       * as one large chunk
109       * @param bufferToAppend
110       * @param off
111       * @param len
112       * @throws IOException
113       * 
114       * @since 3.0
115       */
116      protected void flushCacheWithAppend(byte bufferToAppend[], int off, int len) throws IOException {
117          byte chunkHeader[] = EncodingUtil.getAsciiBytes(
118                  Integer.toHexString(cachePosition + len) + "\r\n");
119          stream.write(chunkHeader, 0, chunkHeader.length);
120          stream.write(cache, 0, cachePosition);
121          stream.write(bufferToAppend, off, len);
122          stream.write(ENDCHUNK, 0, ENDCHUNK.length);
123          cachePosition = 0;
124      }
125  
126      protected void writeClosingChunk() throws IOException {
127          // Write the final chunk.
128  
129          stream.write(ZERO, 0, ZERO.length);
130          stream.write(CRLF, 0, CRLF.length);
131          stream.write(ENDCHUNK, 0, ENDCHUNK.length);
132      }
133  
134      // ----------------------------------------------------------- Public Methods
135      /**
136       * Must be called to ensure the internal cache is flushed and the closing chunk is written.
137       * @throws IOException
138       * 
139       * @since 3.0
140       */
141      public void finish() throws IOException {
142          if (!wroteLastChunk) {
143              flushCache();
144              writeClosingChunk();
145              wroteLastChunk = true;
146          }
147      }
148  
149      // -------------------------------------------- OutputStream Methods
150      /**
151       * Write the specified byte to our output stream.
152       * 
153       * Note: Avoid this method as it will cause an inefficient single byte chunk. 
154       * Use write (byte[], int, int) instead.
155       * 
156       * @param b The byte to be written
157       * @throws IOException if an input/output error occurs
158       */
159      public void write(int b) throws IOException {
160          cache[cachePosition] = (byte) b;
161          cachePosition++;
162          if (cachePosition == cache.length) flushCache();
163      }
164  
165      /**
166       * Writes the array. If the array does not fit within the buffer, it is
167       * not split, but rather written out as one large chunk.
168       * @param b
169       * @throws IOException
170       * 
171       * @since 3.0
172       */
173      public void write(byte b[]) throws IOException {
174          this.write(b, 0, b.length);
175      }
176  
177      public void write(byte src[], int off, int len) throws IOException {
178          if (len >= cache.length - cachePosition) {
179              flushCacheWithAppend(src, off, len);
180          } else {
181              System.arraycopy(src, off, cache, cachePosition, len);
182              cachePosition += len;
183          }
184      }
185  
186      /**
187       * Flushes the underlying stream, but leaves the internal buffer alone.
188       * @throws IOException
189       */
190      public void flush() throws IOException {
191          stream.flush();
192      }
193  
194      /**
195       * Finishes writing to the underlying stream, but does NOT close the underlying stream.
196       * @throws IOException
197       */
198      public void close() throws IOException {
199          finish();
200          super.close();
201      }
202  }