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.http.impl.io;
29
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.nio.ByteBuffer;
33 import java.nio.CharBuffer;
34 import java.nio.charset.CharsetDecoder;
35 import java.nio.charset.CoderResult;
36
37 import org.apache.http.MessageConstraintException;
38 import org.apache.http.config.MessageConstraints;
39 import org.apache.http.io.BufferInfo;
40 import org.apache.http.io.HttpTransportMetrics;
41 import org.apache.http.io.SessionInputBuffer;
42 import org.apache.http.protocol.HTTP;
43 import org.apache.http.util.Args;
44 import org.apache.http.util.Asserts;
45 import org.apache.http.util.ByteArrayBuffer;
46 import org.apache.http.util.CharArrayBuffer;
47
48
49
50
51
52
53
54
55
56
57
58
59 public class SessionInputBufferImpl implements SessionInputBuffer, BufferInfo {
60
61 private final HttpTransportMetricsImpl metrics;
62 private final byte[] buffer;
63 private final ByteArrayBuffer lineBuffer;
64 private final int minChunkLimit;
65 private final MessageConstraints constraints;
66 private final CharsetDecoder decoder;
67
68 private InputStream inStream;
69 private int bufferPos;
70 private int bufferLen;
71 private CharBuffer cbuf;
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88 public SessionInputBufferImpl(
89 final HttpTransportMetricsImpl metrics,
90 final int bufferSize,
91 final int minChunkLimit,
92 final MessageConstraints constraints,
93 final CharsetDecoder charDecoder) {
94 Args.notNull(metrics, "HTTP transport metrcis");
95 Args.positive(bufferSize, "Buffer size");
96 this.metrics = metrics;
97 this.buffer = new byte[bufferSize];
98 this.bufferPos = 0;
99 this.bufferLen = 0;
100 this.minChunkLimit = minChunkLimit >= 0 ? minChunkLimit : 512;
101 this.constraints = constraints != null ? constraints : MessageConstraints.DEFAULT;
102 this.lineBuffer = new ByteArrayBuffer(bufferSize);
103 this.decoder = charDecoder;
104 }
105
106 public SessionInputBufferImpl(
107 final HttpTransportMetricsImpl metrics,
108 final int bufferSize) {
109 this(metrics, bufferSize, bufferSize, null, null);
110 }
111
112 public void bind(final InputStream inputStream) {
113 this.inStream = inputStream;
114 }
115
116 public boolean isBound() {
117 return this.inStream != null;
118 }
119
120 @Override
121 public int capacity() {
122 return this.buffer.length;
123 }
124
125 @Override
126 public int length() {
127 return this.bufferLen - this.bufferPos;
128 }
129
130 @Override
131 public int available() {
132 return capacity() - length();
133 }
134
135 private int streamRead(final byte[] b, final int off, final int len) throws IOException {
136 Asserts.notNull(this.inStream, "Input stream");
137 return this.inStream.read(b, off, len);
138 }
139
140 public int fillBuffer() throws IOException {
141
142 if (this.bufferPos > 0) {
143 final int len = this.bufferLen - this.bufferPos;
144 if (len > 0) {
145 System.arraycopy(this.buffer, this.bufferPos, this.buffer, 0, len);
146 }
147 this.bufferPos = 0;
148 this.bufferLen = len;
149 }
150 final int readLen;
151 final int off = this.bufferLen;
152 final int len = this.buffer.length - off;
153 readLen = streamRead(this.buffer, off, len);
154 if (readLen == -1) {
155 return -1;
156 }
157 this.bufferLen = off + readLen;
158 this.metrics.incrementBytesTransferred(readLen);
159 return readLen;
160 }
161
162 public boolean hasBufferedData() {
163 return this.bufferPos < this.bufferLen;
164 }
165
166 public void clear() {
167 this.bufferPos = 0;
168 this.bufferLen = 0;
169 }
170
171 @Override
172 public int read() throws IOException {
173 int noRead;
174 while (!hasBufferedData()) {
175 noRead = fillBuffer();
176 if (noRead == -1) {
177 return -1;
178 }
179 }
180 return this.buffer[this.bufferPos++] & 0xff;
181 }
182
183 @Override
184 public int read(final byte[] b, final int off, final int len) throws IOException {
185 if (b == null) {
186 return 0;
187 }
188 if (hasBufferedData()) {
189 final int chunk = Math.min(len, this.bufferLen - this.bufferPos);
190 System.arraycopy(this.buffer, this.bufferPos, b, off, chunk);
191 this.bufferPos += chunk;
192 return chunk;
193 }
194
195
196 if (len > this.minChunkLimit) {
197 final int readLen = streamRead(b, off, len);
198 if (readLen > 0) {
199 this.metrics.incrementBytesTransferred(readLen);
200 }
201 return readLen;
202 }
203
204 while (!hasBufferedData()) {
205 final int noRead = fillBuffer();
206 if (noRead == -1) {
207 return -1;
208 }
209 }
210 final int chunk = Math.min(len, this.bufferLen - this.bufferPos);
211 System.arraycopy(this.buffer, this.bufferPos, b, off, chunk);
212 this.bufferPos += chunk;
213 return chunk;
214 }
215
216 @Override
217 public int read(final byte[] b) throws IOException {
218 if (b == null) {
219 return 0;
220 }
221 return read(b, 0, b.length);
222 }
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239 @Override
240 public int readLine(final CharArrayBuffer charbuffer) throws IOException {
241 Args.notNull(charbuffer, "Char array buffer");
242 final int maxLineLen = this.constraints.getMaxLineLength();
243 int noRead = 0;
244 boolean retry = true;
245 while (retry) {
246
247 int pos = -1;
248 for (int i = this.bufferPos; i < this.bufferLen; i++) {
249 if (this.buffer[i] == HTTP.LF) {
250 pos = i;
251 break;
252 }
253 }
254
255 if (maxLineLen > 0) {
256 final int currentLen = this.lineBuffer.length()
257 + (pos >= 0 ? pos : this.bufferLen) - this.bufferPos;
258 if (currentLen >= maxLineLen) {
259 throw new MessageConstraintException("Maximum line length limit exceeded");
260 }
261 }
262
263 if (pos != -1) {
264
265 if (this.lineBuffer.isEmpty()) {
266
267 return lineFromReadBuffer(charbuffer, pos);
268 }
269 retry = false;
270 final int len = pos + 1 - this.bufferPos;
271 this.lineBuffer.append(this.buffer, this.bufferPos, len);
272 this.bufferPos = pos + 1;
273 } else {
274
275 if (hasBufferedData()) {
276 final int len = this.bufferLen - this.bufferPos;
277 this.lineBuffer.append(this.buffer, this.bufferPos, len);
278 this.bufferPos = this.bufferLen;
279 }
280 noRead = fillBuffer();
281 if (noRead == -1) {
282 retry = false;
283 }
284 }
285 }
286 if (noRead == -1 && this.lineBuffer.isEmpty()) {
287
288 return -1;
289 }
290 return lineFromLineBuffer(charbuffer);
291 }
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306 private int lineFromLineBuffer(final CharArrayBuffer charbuffer)
307 throws IOException {
308
309 int len = this.lineBuffer.length();
310 if (len > 0) {
311 if (this.lineBuffer.byteAt(len - 1) == HTTP.LF) {
312 len--;
313 }
314
315 if (len > 0) {
316 if (this.lineBuffer.byteAt(len - 1) == HTTP.CR) {
317 len--;
318 }
319 }
320 }
321 if (this.decoder == null) {
322 charbuffer.append(this.lineBuffer, 0, len);
323 } else {
324 final ByteBuffer bbuf = ByteBuffer.wrap(this.lineBuffer.buffer(), 0, len);
325 len = appendDecoded(charbuffer, bbuf);
326 }
327 this.lineBuffer.clear();
328 return len;
329 }
330
331 private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position)
332 throws IOException {
333 int pos = position;
334 final int off = this.bufferPos;
335 int len;
336 this.bufferPos = pos + 1;
337 if (pos > off && this.buffer[pos - 1] == HTTP.CR) {
338
339 pos--;
340 }
341 len = pos - off;
342 if (this.decoder == null) {
343 charbuffer.append(this.buffer, off, len);
344 } else {
345 final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len);
346 len = appendDecoded(charbuffer, bbuf);
347 }
348 return len;
349 }
350
351 private int appendDecoded(
352 final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException {
353 if (!bbuf.hasRemaining()) {
354 return 0;
355 }
356 if (this.cbuf == null) {
357 this.cbuf = CharBuffer.allocate(1024);
358 }
359 this.decoder.reset();
360 int len = 0;
361 while (bbuf.hasRemaining()) {
362 final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true);
363 len += handleDecodingResult(result, charbuffer, bbuf);
364 }
365 final CoderResult result = this.decoder.flush(this.cbuf);
366 len += handleDecodingResult(result, charbuffer, bbuf);
367 this.cbuf.clear();
368 return len;
369 }
370
371 private int handleDecodingResult(
372 final CoderResult result,
373 final CharArrayBuffer charbuffer,
374 final ByteBuffer bbuf) throws IOException {
375 if (result.isError()) {
376 result.throwException();
377 }
378 this.cbuf.flip();
379 final int len = this.cbuf.remaining();
380 while (this.cbuf.hasRemaining()) {
381 charbuffer.append(this.cbuf.get());
382 }
383 this.cbuf.compact();
384 return len;
385 }
386
387 @Override
388 public String readLine() throws IOException {
389 final CharArrayBuffertml#CharArrayBuffer">CharArrayBuffer charbuffer = new CharArrayBuffer(64);
390 final int readLen = readLine(charbuffer);
391 return readLen != -1 ? charbuffer.toString() : null;
392 }
393
394 @Override
395 public boolean isDataAvailable(final int timeout) throws IOException {
396 return hasBufferedData();
397 }
398
399 @Override
400 public HttpTransportMetrics getMetrics() {
401 return this.metrics;
402 }
403
404 }