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 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
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
55 private OutputStream stream = null;
56
57 private byte[] cache;
58
59 private int cachePosition = 0;
60
61 private boolean wroteLastChunk = false;
62
63
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
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
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
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
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 }