mobile/android/thirdparty/ch/boye/httpclientandroidlib/impl/io/AbstractSessionInputBuffer.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

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

mercurial