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 = 0;
62 private boolean wroteLastChunk = false;
63 private boolean closed = false;
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 int chunkSizeHint,
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 = new byte[chunkSizeHint > 0 ? chunkSizeHint : 2048];
86 this.lineBuffer = new CharArrayBuffer(32);
87 this.trailerSupplier = trailerSupplier;
88 }
89
90
91
92
93
94
95
96
97 public ChunkedOutputStream(final SessionOutputBuffer buffer, final OutputStream outputStream, final int chunkSizeHint) {
98 this(buffer, outputStream, chunkSizeHint, null);
99 }
100
101
102
103
104 private void flushCache() throws IOException {
105 if (this.cachePosition > 0) {
106 this.lineBuffer.clear();
107 this.lineBuffer.append(Integer.toHexString(this.cachePosition));
108 this.buffer.writeLine(this.lineBuffer, this.outputStream);
109 this.buffer.write(this.cache, 0, this.cachePosition, this.outputStream);
110 this.lineBuffer.clear();
111 this.buffer.writeLine(this.lineBuffer, this.outputStream);
112 this.cachePosition = 0;
113 }
114 }
115
116
117
118
119
120 private void flushCacheWithAppend(final byte[] bufferToAppend, final int off, final int len) throws IOException {
121 this.lineBuffer.clear();
122 this.lineBuffer.append(Integer.toHexString(this.cachePosition + len));
123 this.buffer.writeLine(this.lineBuffer, this.outputStream);
124 this.buffer.write(this.cache, 0, this.cachePosition, this.outputStream);
125 this.buffer.write(bufferToAppend, off, len, this.outputStream);
126 this.lineBuffer.clear();
127 this.buffer.writeLine(this.lineBuffer, this.outputStream);
128 this.cachePosition = 0;
129 }
130
131 private void writeClosingChunk() throws IOException {
132
133 this.lineBuffer.clear();
134 this.lineBuffer.append('0');
135 this.buffer.writeLine(this.lineBuffer, this.outputStream);
136 writeTrailers();
137 this.lineBuffer.clear();
138 this.buffer.writeLine(this.lineBuffer, this.outputStream);
139 }
140
141 private void writeTrailers() throws IOException {
142 final List<? extends Header> trailers = this.trailerSupplier != null ? this.trailerSupplier.get() : null;
143 if (trailers != null) {
144 for (int i = 0; i < trailers.size(); i++) {
145 final Header header = trailers.get(i);
146 if (header instanceof FormattedHeader) {
147 final CharArrayBuffer chbuffer = ((FormattedHeader) header).getBuffer();
148 this.buffer.writeLine(chbuffer, this.outputStream);
149 } else {
150 this.lineBuffer.clear();
151 BasicLineFormatter.INSTANCE.formatHeader(this.lineBuffer, header);
152 this.buffer.writeLine(this.lineBuffer, this.outputStream);
153 }
154 }
155 }
156 }
157
158
159
160
161
162
163
164 public void finish() throws IOException {
165 if (!this.wroteLastChunk) {
166 flushCache();
167 writeClosingChunk();
168 this.wroteLastChunk = true;
169 }
170 }
171
172
173 @Override
174 public void write(final int b) throws IOException {
175 if (this.closed) {
176 throw new StreamClosedException();
177 }
178 this.cache[this.cachePosition] = (byte) b;
179 this.cachePosition++;
180 if (this.cachePosition == this.cache.length) {
181 flushCache();
182 }
183 }
184
185
186
187
188
189 @Override
190 public void write(final byte[] b) throws IOException {
191 write(b, 0, b.length);
192 }
193
194
195
196
197
198 @Override
199 public void write(final byte[] src, final int off, final int len) throws IOException {
200 if (this.closed) {
201 throw new StreamClosedException();
202 }
203 if (len >= this.cache.length - this.cachePosition) {
204 flushCacheWithAppend(src, off, len);
205 } else {
206 System.arraycopy(src, off, cache, this.cachePosition, len);
207 this.cachePosition += len;
208 }
209 }
210
211
212
213
214 @Override
215 public void flush() throws IOException {
216 flushCache();
217 this.buffer.flush(this.outputStream);
218 }
219
220
221
222
223 @Override
224 public void close() throws IOException {
225 if (!this.closed) {
226 this.closed = true;
227 finish();
228 this.buffer.flush(this.outputStream);
229 }
230 }
231 }