Wed, 31 Dec 2014 07:22:50 +0100
Correct previous dual key logic pending first delivery installment.
1 /*
2 * ====================================================================
3 * Licensed to the Apache Software Foundation (ASF) under one
4 * or more contributor license agreements. See the NOTICE file
5 * distributed with this work for additional information
6 * regarding copyright ownership. The ASF licenses this file
7 * to you under the Apache License, Version 2.0 (the
8 * "License"); you may not use this file except in compliance
9 * with the License. You may obtain a copy of the License at
10 *
11 * http://www.apache.org/licenses/LICENSE-2.0
12 *
13 * Unless required by applicable law or agreed to in writing,
14 * software distributed under the License is distributed on an
15 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 * KIND, either express or implied. See the License for the
17 * specific language governing permissions and limitations
18 * under the License.
19 * ====================================================================
20 *
21 * This software consists of voluntary contributions made by many
22 * individuals on behalf of the Apache Software Foundation. For more
23 * information on the Apache Software Foundation, please see
24 * <http://www.apache.org/>.
25 *
26 */
28 package ch.boye.httpclientandroidlib.impl.io;
30 import java.io.IOException;
31 import java.io.InputStream;
33 import ch.boye.httpclientandroidlib.io.BufferInfo;
34 import ch.boye.httpclientandroidlib.io.SessionInputBuffer;
35 import ch.boye.httpclientandroidlib.io.HttpTransportMetrics;
36 import ch.boye.httpclientandroidlib.params.CoreConnectionPNames;
37 import ch.boye.httpclientandroidlib.params.HttpParams;
38 import ch.boye.httpclientandroidlib.params.HttpProtocolParams;
39 import ch.boye.httpclientandroidlib.protocol.HTTP;
40 import ch.boye.httpclientandroidlib.util.ByteArrayBuffer;
41 import ch.boye.httpclientandroidlib.util.CharArrayBuffer;
43 /**
44 * Abstract base class for session input buffers that stream data from
45 * an arbitrary {@link InputStream}. This class buffers input data in
46 * an internal byte array for optimal input performance.
47 * <p>
48 * {@link #readLine(CharArrayBuffer)} and {@link #readLine()} methods of this
49 * class treat a lone LF as valid line delimiters in addition to CR-LF required
50 * by the HTTP specification.
51 *
52 * <p>
53 * The following parameters can be used to customize the behavior of this
54 * class:
55 * <ul>
56 * <li>{@link ch.boye.httpclientandroidlib.params.CoreProtocolPNames#HTTP_ELEMENT_CHARSET}</li>
57 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MAX_LINE_LENGTH}</li>
58 * <li>{@link ch.boye.httpclientandroidlib.params.CoreConnectionPNames#MIN_CHUNK_LIMIT}</li>
59 * </ul>
60 * @since 4.0
61 */
62 public abstract class AbstractSessionInputBuffer implements SessionInputBuffer, BufferInfo {
64 private InputStream instream;
65 private byte[] buffer;
66 private int bufferpos;
67 private int bufferlen;
69 private ByteArrayBuffer linebuffer = null;
71 private String charset = HTTP.US_ASCII;
72 private boolean ascii = true;
73 private int maxLineLen = -1;
74 private int minChunkLimit = 512;
76 private HttpTransportMetricsImpl metrics;
78 /**
79 * Initializes this session input buffer.
80 *
81 * @param instream the source input stream.
82 * @param buffersize the size of the internal buffer.
83 * @param params HTTP parameters.
84 */
85 protected void init(final InputStream instream, int buffersize, final HttpParams params) {
86 if (instream == null) {
87 throw new IllegalArgumentException("Input stream may not be null");
88 }
89 if (buffersize <= 0) {
90 throw new IllegalArgumentException("Buffer size may not be negative or zero");
91 }
92 if (params == null) {
93 throw new IllegalArgumentException("HTTP parameters may not be null");
94 }
95 this.instream = instream;
96 this.buffer = new byte[buffersize];
97 this.bufferpos = 0;
98 this.bufferlen = 0;
99 this.linebuffer = new ByteArrayBuffer(buffersize);
100 this.charset = HttpProtocolParams.getHttpElementCharset(params);
101 this.ascii = this.charset.equalsIgnoreCase(HTTP.US_ASCII)
102 || this.charset.equalsIgnoreCase(HTTP.ASCII);
103 this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1);
104 this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512);
105 this.metrics = createTransportMetrics();
106 }
108 /**
109 * @since 4.1
110 */
111 protected HttpTransportMetricsImpl createTransportMetrics() {
112 return new HttpTransportMetricsImpl();
113 }
115 /**
116 * @since 4.1
117 */
118 public int capacity() {
119 return this.buffer.length;
120 }
122 /**
123 * @since 4.1
124 */
125 public int length() {
126 return this.bufferlen - this.bufferpos;
127 }
129 /**
130 * @since 4.1
131 */
132 public int available() {
133 return capacity() - length();
134 }
136 protected int fillBuffer() throws IOException {
137 // compact the buffer if necessary
138 if (this.bufferpos > 0) {
139 int len = this.bufferlen - this.bufferpos;
140 if (len > 0) {
141 System.arraycopy(this.buffer, this.bufferpos, this.buffer, 0, len);
142 }
143 this.bufferpos = 0;
144 this.bufferlen = len;
145 }
146 int l;
147 int off = this.bufferlen;
148 int len = this.buffer.length - off;
149 l = this.instream.read(this.buffer, off, len);
150 if (l == -1) {
151 return -1;
152 } else {
153 this.bufferlen = off + l;
154 this.metrics.incrementBytesTransferred(l);
155 return l;
156 }
157 }
159 protected boolean hasBufferedData() {
160 return this.bufferpos < this.bufferlen;
161 }
163 public int read() throws IOException {
164 int noRead = 0;
165 while (!hasBufferedData()) {
166 noRead = fillBuffer();
167 if (noRead == -1) {
168 return -1;
169 }
170 }
171 return this.buffer[this.bufferpos++] & 0xff;
172 }
174 public int read(final byte[] b, int off, int len) throws IOException {
175 if (b == null) {
176 return 0;
177 }
178 if (hasBufferedData()) {
179 int chunk = Math.min(len, this.bufferlen - this.bufferpos);
180 System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
181 this.bufferpos += chunk;
182 return chunk;
183 }
184 // If the remaining capacity is big enough, read directly from the
185 // underlying input stream bypassing the buffer.
186 if (len > this.minChunkLimit) {
187 int read = this.instream.read(b, off, len);
188 if (read > 0) {
189 this.metrics.incrementBytesTransferred(read);
190 }
191 return read;
192 } else {
193 // otherwise read to the buffer first
194 while (!hasBufferedData()) {
195 int noRead = fillBuffer();
196 if (noRead == -1) {
197 return -1;
198 }
199 }
200 int chunk = Math.min(len, this.bufferlen - this.bufferpos);
201 System.arraycopy(this.buffer, this.bufferpos, b, off, chunk);
202 this.bufferpos += chunk;
203 return chunk;
204 }
205 }
207 public int read(final byte[] b) throws IOException {
208 if (b == null) {
209 return 0;
210 }
211 return read(b, 0, b.length);
212 }
214 private int locateLF() {
215 for (int i = this.bufferpos; i < this.bufferlen; i++) {
216 if (this.buffer[i] == HTTP.LF) {
217 return i;
218 }
219 }
220 return -1;
221 }
223 /**
224 * Reads a complete line of characters up to a line delimiter from this
225 * session buffer into the given line buffer. The number of chars actually
226 * read is returned as an integer. The line delimiter itself is discarded.
227 * If no char is available because the end of the stream has been reached,
228 * the value <code>-1</code> is returned. This method blocks until input
229 * data is available, end of file is detected, or an exception is thrown.
230 * <p>
231 * This method treats a lone LF as a valid line delimiters in addition
232 * to CR-LF required by the HTTP specification.
233 *
234 * @param charbuffer the line buffer.
235 * @return one line of characters
236 * @exception IOException if an I/O error occurs.
237 */
238 public int readLine(final CharArrayBuffer charbuffer) throws IOException {
239 if (charbuffer == null) {
240 throw new IllegalArgumentException("Char array buffer may not be null");
241 }
242 int noRead = 0;
243 boolean retry = true;
244 while (retry) {
245 // attempt to find end of line (LF)
246 int i = locateLF();
247 if (i != -1) {
248 // end of line found.
249 if (this.linebuffer.isEmpty()) {
250 // the entire line is preset in the read buffer
251 return lineFromReadBuffer(charbuffer, i);
252 }
253 retry = false;
254 int len = i + 1 - this.bufferpos;
255 this.linebuffer.append(this.buffer, this.bufferpos, len);
256 this.bufferpos = i + 1;
257 } else {
258 // end of line not found
259 if (hasBufferedData()) {
260 int len = this.bufferlen - this.bufferpos;
261 this.linebuffer.append(this.buffer, this.bufferpos, len);
262 this.bufferpos = this.bufferlen;
263 }
264 noRead = fillBuffer();
265 if (noRead == -1) {
266 retry = false;
267 }
268 }
269 if (this.maxLineLen > 0 && this.linebuffer.length() >= this.maxLineLen) {
270 throw new IOException("Maximum line length limit exceeded");
271 }
272 }
273 if (noRead == -1 && this.linebuffer.isEmpty()) {
274 // indicate the end of stream
275 return -1;
276 }
277 return lineFromLineBuffer(charbuffer);
278 }
280 /**
281 * Reads a complete line of characters up to a line delimiter from this
282 * session buffer. The line delimiter itself is discarded. If no char is
283 * available because the end of the stream has been reached,
284 * <code>null</code> is returned. This method blocks until input data is
285 * available, end of file is detected, or an exception is thrown.
286 * <p>
287 * This method treats a lone LF as a valid line delimiters in addition
288 * to CR-LF required by the HTTP specification.
289 *
290 * @return HTTP line as a string
291 * @exception IOException if an I/O error occurs.
292 */
293 private int lineFromLineBuffer(final CharArrayBuffer charbuffer)
294 throws IOException {
295 // discard LF if found
296 int l = this.linebuffer.length();
297 if (l > 0) {
298 if (this.linebuffer.byteAt(l - 1) == HTTP.LF) {
299 l--;
300 this.linebuffer.setLength(l);
301 }
302 // discard CR if found
303 if (l > 0) {
304 if (this.linebuffer.byteAt(l - 1) == HTTP.CR) {
305 l--;
306 this.linebuffer.setLength(l);
307 }
308 }
309 }
310 l = this.linebuffer.length();
311 if (this.ascii) {
312 charbuffer.append(this.linebuffer, 0, l);
313 } else {
314 // This is VERY memory inefficient, BUT since non-ASCII charsets are
315 // NOT meant to be used anyway, there's no point optimizing it
316 String s = new String(this.linebuffer.buffer(), 0, l, this.charset);
317 l = s.length();
318 charbuffer.append(s);
319 }
320 this.linebuffer.clear();
321 return l;
322 }
324 private int lineFromReadBuffer(final CharArrayBuffer charbuffer, int pos)
325 throws IOException {
326 int off = this.bufferpos;
327 int len;
328 this.bufferpos = pos + 1;
329 if (pos > 0 && this.buffer[pos - 1] == HTTP.CR) {
330 // skip CR if found
331 pos--;
332 }
333 len = pos - off;
334 if (this.ascii) {
335 charbuffer.append(this.buffer, off, len);
336 } else {
337 // This is VERY memory inefficient, BUT since non-ASCII charsets are
338 // NOT meant to be used anyway, there's no point optimizing it
339 String s = new String(this.buffer, off, len, this.charset);
340 charbuffer.append(s);
341 len = s.length();
342 }
343 return len;
344 }
346 public String readLine() throws IOException {
347 CharArrayBuffer charbuffer = new CharArrayBuffer(64);
348 int l = readLine(charbuffer);
349 if (l != -1) {
350 return charbuffer.toString();
351 } else {
352 return null;
353 }
354 }
356 public HttpTransportMetrics getMetrics() {
357 return this.metrics;
358 }
360 }