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 package org.apache.hc.core5.http.impl.io;
29
30 import java.io.IOException;
31 import java.io.OutputStream;
32 import java.util.List;
33
34 import org.apache.hc.core5.function.Supplier;
35 import org.apache.hc.core5.http.FormattedHeader;
36 import org.apache.hc.core5.http.Header;
37 import org.apache.hc.core5.http.StreamClosedException;
38 import org.apache.hc.core5.http.io.SessionOutputBuffer;
39 import org.apache.hc.core5.http.message.BasicLineFormatter;
40 import org.apache.hc.core5.util.Args;
41 import org.apache.hc.core5.util.CharArrayBuffer;
42
43
44
45
46
47
48
49
50
51
52
53
54
55 public class ChunkedOutputStream extends OutputStream {
56
57 private final SessionOutputBuffer buffer;
58 private final OutputStream outputStream;
59
60 private final byte[] cache;
61 private int cachePosition;
62 private boolean wroteLastChunk;
63 private boolean closed;
64 private final CharArrayBuffer lineBuffer;
65 private final Supplier<List<? extends Header>> trailerSupplier;
66
67
68
69
70
71
72
73
74
75
76
77 public ChunkedOutputStream(
78 final SessionOutputBuffer buffer,
79 final OutputStream outputStream,
80 final byte[] chunkCache,
81 final Supplier<List<? extends Header>> trailerSupplier) {
82 super();
83 this.buffer = Args.notNull(buffer, "Session output buffer");
84 this.outputStream = Args.notNull(outputStream, "Output stream");
85 this.cache = Args.notNull(chunkCache, "Chunk cache");
86 this.lineBuffer = new CharArrayBuffer(32);
87 this.trailerSupplier = trailerSupplier;
88 }
89
90
91
92
93
94
95
96
97
98
99
100 public ChunkedOutputStream(
101 final SessionOutputBuffer buffer,
102 final OutputStream outputStream,
103 final int chunkSizeHint,
104 final Supplier<List<? extends Header>> trailerSupplier) {
105 this(buffer, outputStream, new byte[chunkSizeHint > 0 ? chunkSizeHint : 8192], trailerSupplier);
106 }
107
108
109
110
111
112
113
114
115 public ChunkedOutputStream(final SessionOutputBuffer buffer, final OutputStream outputStream, final int chunkSizeHint) {
116 this(buffer, outputStream, chunkSizeHint, null);
117 }
118
119
120
121
122 private void flushCache() throws IOException {
123 if (this.cachePosition > 0) {
124 this.lineBuffer.clear();
125 this.lineBuffer.append(Integer.toHexString(this.cachePosition));
126 this.buffer.writeLine(this.lineBuffer, this.outputStream);
127 this.buffer.write(this.cache, 0, this.cachePosition, this.outputStream);
128 this.lineBuffer.clear();
129 this.buffer.writeLine(this.lineBuffer, this.outputStream);
130 this.cachePosition = 0;
131 }
132 }
133
134
135
136
137
138 private void flushCacheWithAppend(final byte[] bufferToAppend, final int off, final int len) throws IOException {
139 this.lineBuffer.clear();
140 this.lineBuffer.append(Integer.toHexString(this.cachePosition + len));
141 this.buffer.writeLine(this.lineBuffer, this.outputStream);
142 this.buffer.write(this.cache, 0, this.cachePosition, this.outputStream);
143 this.buffer.write(bufferToAppend, off, len, this.outputStream);
144 this.lineBuffer.clear();
145 this.buffer.writeLine(this.lineBuffer, this.outputStream);
146 this.cachePosition = 0;
147 }
148
149 private void writeClosingChunk() throws IOException {
150
151 this.lineBuffer.clear();
152 this.lineBuffer.append('0');
153 this.buffer.writeLine(this.lineBuffer, this.outputStream);
154 writeTrailers();
155 this.lineBuffer.clear();
156 this.buffer.writeLine(this.lineBuffer, this.outputStream);
157 }
158
159 private void writeTrailers() throws IOException {
160 final List<? extends Header> trailers = this.trailerSupplier != null ? this.trailerSupplier.get() : null;
161 if (trailers != null) {
162 for (int i = 0; i < trailers.size(); i++) {
163 final Header header = trailers.get(i);
164 if (header instanceof FormattedHeader) {
165 final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer();
166 this.buffer.writeLine(chbuffer, this.outputStream);
167 } else {
168 this.lineBuffer.clear();
169 BasicLineFormatter.INSTANCE.formatHeader(this.lineBuffer, header);
170 this.buffer.writeLine(this.lineBuffer, this.outputStream);
171 }
172 }
173 }
174 }
175
176
177
178
179
180
181
182 public void finish() throws IOException {
183 if (!this.wroteLastChunk) {
184 flushCache();
185 writeClosingChunk();
186 this.wroteLastChunk = true;
187 }
188 }
189
190
191 @Override
192 public void write(final int b) throws IOException {
193 if (this.closed) {
194 throw new StreamClosedException();
195 }
196 this.cache[this.cachePosition] = (byte) b;
197 this.cachePosition++;
198 if (this.cachePosition == this.cache.length) {
199 flushCache();
200 }
201 }
202
203
204
205
206
207 @Override
208 public void write(final byte[] b) throws IOException {
209 write(b, 0, b.length);
210 }
211
212
213
214
215
216 @Override
217 public void write(final byte[] src, final int off, final int len) throws IOException {
218 if (this.closed) {
219 throw new StreamClosedException();
220 }
221 if (len >= this.cache.length - this.cachePosition) {
222 flushCacheWithAppend(src, off, len);
223 } else {
224 System.arraycopy(src, off, cache, this.cachePosition, len);
225 this.cachePosition += len;
226 }
227 }
228
229
230
231
232 @Override
233 public void flush() throws IOException {
234 flushCache();
235 this.buffer.flush(this.outputStream);
236 }
237
238
239
240
241 @Override
242 public void close() throws IOException {
243 if (!this.closed) {
244 this.closed = true;
245 finish();
246 this.buffer.flush(this.outputStream);
247 }
248 }
249 }