View Javadoc

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