1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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
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
56 private OutputStream stream = null;
57
58 private byte[] cache;
59
60 private int cachePosition = 0;
61
62 private boolean wroteLastChunk = false;
63
64
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
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
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
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
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 }