All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.http.impl.io.AbstractSessionInputBuffer Maven / Gradle / Ivy

There is a newer version: 5.0.22
Show newest version
/*
 * ====================================================================
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * .
 *
 */

package org.apache.http.impl.io;

import java.io.IOException;
import java.io.InputStream;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.CoderResult;
import java.nio.charset.CodingErrorAction;

import org.apache.http.Consts;
import org.apache.http.io.BufferInfo;
import org.apache.http.io.HttpTransportMetrics;
import org.apache.http.io.SessionInputBuffer;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.protocol.HTTP;
import org.apache.http.util.Args;
import org.apache.http.util.ByteArrayBuffer;
import org.apache.http.util.CharArrayBuffer;

/**
 * Abstract base class for session input buffers that stream data from
 * an arbitrary {@link InputStream}. This class buffers input data in
 * an internal byte array for optimal input performance.
 * 

* {@link #readLine(CharArrayBuffer)} and {@link #readLine()} methods of this * class treat a lone LF as valid line delimiters in addition to CR-LF required * by the HTTP specification. * * @since 4.0 * * @deprecated (4.3) use {@link SessionInputBufferImpl} */ @Deprecated public abstract class AbstractSessionInputBuffer implements SessionInputBuffer, BufferInfo { private InputStream inStream; private byte[] buffer; private ByteArrayBuffer lineBuffer; private Charset charset; private boolean ascii; private int maxLineLen; private int minChunkLimit; private HttpTransportMetricsImpl metrics; private CodingErrorAction onMalformedCharAction; private CodingErrorAction onUnmappableCharAction; private int bufferPos; private int bufferLen; private CharsetDecoder decoder; private CharBuffer cbuf; public AbstractSessionInputBuffer() { } /** * Initializes this session input buffer. * * @param inputStream the source input stream. * @param bufferSize the size of the internal buffer. * @param params HTTP parameters. */ protected void init(final InputStream inputStream, final int bufferSize, final HttpParams params) { Args.notNull(inputStream, "Input stream"); Args.notNegative(bufferSize, "Buffer size"); Args.notNull(params, "HTTP parameters"); this.inStream = inputStream; this.buffer = new byte[bufferSize]; this.bufferPos = 0; this.bufferLen = 0; this.lineBuffer = new ByteArrayBuffer(bufferSize); final String charset = (String) params.getParameter(CoreProtocolPNames.HTTP_ELEMENT_CHARSET); this.charset = charset != null ? Charset.forName(charset) : Consts.ASCII; this.ascii = this.charset.equals(Consts.ASCII); this.decoder = null; this.maxLineLen = params.getIntParameter(CoreConnectionPNames.MAX_LINE_LENGTH, -1); this.minChunkLimit = params.getIntParameter(CoreConnectionPNames.MIN_CHUNK_LIMIT, 512); this.metrics = createTransportMetrics(); final CodingErrorAction a1 = (CodingErrorAction) params.getParameter( CoreProtocolPNames.HTTP_MALFORMED_INPUT_ACTION); this.onMalformedCharAction = a1 != null ? a1 : CodingErrorAction.REPORT; final CodingErrorAction a2 = (CodingErrorAction) params.getParameter( CoreProtocolPNames.HTTP_UNMAPPABLE_INPUT_ACTION); this.onUnmappableCharAction = a2 != null? a2 : CodingErrorAction.REPORT; } /** * @since 4.1 */ protected HttpTransportMetricsImpl createTransportMetrics() { return new HttpTransportMetricsImpl(); } /** * @since 4.1 */ @Override public int capacity() { return this.buffer.length; } /** * @since 4.1 */ @Override public int length() { return this.bufferLen - this.bufferPos; } /** * @since 4.1 */ @Override public int available() { return capacity() - length(); } protected int fillBuffer() throws IOException { // compact the buffer if necessary if (this.bufferPos > 0) { final int len = this.bufferLen - this.bufferPos; if (len > 0) { System.arraycopy(this.buffer, this.bufferPos, this.buffer, 0, len); } this.bufferPos = 0; this.bufferLen = len; } final int readLen; final int off = this.bufferLen; final int len = this.buffer.length - off; readLen = this.inStream.read(this.buffer, off, len); if (readLen == -1) { return -1; } this.bufferLen = off + readLen; this.metrics.incrementBytesTransferred(readLen); return readLen; } protected boolean hasBufferedData() { return this.bufferPos < this.bufferLen; } @Override public int read() throws IOException { int noRead; while (!hasBufferedData()) { noRead = fillBuffer(); if (noRead == -1) { return -1; } } return this.buffer[this.bufferPos++] & 0xff; } @Override public int read(final byte[] b, final int off, final int len) throws IOException { if (b == null) { return 0; } if (hasBufferedData()) { final int chunk = Math.min(len, this.bufferLen - this.bufferPos); System.arraycopy(this.buffer, this.bufferPos, b, off, chunk); this.bufferPos += chunk; return chunk; } // If the remaining capacity is big enough, read directly from the // underlying input stream bypassing the buffer. if (len > this.minChunkLimit) { final int read = this.inStream.read(b, off, len); if (read > 0) { this.metrics.incrementBytesTransferred(read); } return read; } // otherwise read to the buffer first while (!hasBufferedData()) { final int noRead = fillBuffer(); if (noRead == -1) { return -1; } } final int chunk = Math.min(len, this.bufferLen - this.bufferPos); System.arraycopy(this.buffer, this.bufferPos, b, off, chunk); this.bufferPos += chunk; return chunk; } @Override public int read(final byte[] b) throws IOException { if (b == null) { return 0; } return read(b, 0, b.length); } private int locateLF() { for (int i = this.bufferPos; i < this.bufferLen; i++) { if (this.buffer[i] == HTTP.LF) { return i; } } return -1; } /** * Reads a complete line of characters up to a line delimiter from this * session buffer into the given line buffer. The number of chars actually * read is returned as an integer. The line delimiter itself is discarded. * If no char is available because the end of the stream has been reached, * the value {@code -1} is returned. This method blocks until input * data is available, end of file is detected, or an exception is thrown. *

* This method treats a lone LF as a valid line delimiters in addition * to CR-LF required by the HTTP specification. * * @param charbuffer the line buffer. * @return one line of characters * @throws IOException if an I/O error occurs. */ @Override public int readLine(final CharArrayBuffer charbuffer) throws IOException { Args.notNull(charbuffer, "Char array buffer"); int noRead = 0; boolean retry = true; while (retry) { // attempt to find end of line (LF) final int i = locateLF(); if (i != -1) { // end of line found. if (this.lineBuffer.isEmpty()) { // the entire line is preset in the read buffer return lineFromReadBuffer(charbuffer, i); } retry = false; final int len = i + 1 - this.bufferPos; this.lineBuffer.append(this.buffer, this.bufferPos, len); this.bufferPos = i + 1; } else { // end of line not found if (hasBufferedData()) { final int len = this.bufferLen - this.bufferPos; this.lineBuffer.append(this.buffer, this.bufferPos, len); this.bufferPos = this.bufferLen; } noRead = fillBuffer(); if (noRead == -1) { retry = false; } } if (this.maxLineLen > 0 && this.lineBuffer.length() >= this.maxLineLen) { throw new IOException("Maximum line length limit exceeded"); } } if (noRead == -1 && this.lineBuffer.isEmpty()) { // indicate the end of stream return -1; } return lineFromLineBuffer(charbuffer); } /** * Reads a complete line of characters up to a line delimiter from this * session buffer. The line delimiter itself is discarded. If no char is * available because the end of the stream has been reached, * {@code null} is returned. This method blocks until input data is * available, end of file is detected, or an exception is thrown. *

* This method treats a lone LF as a valid line delimiters in addition * to CR-LF required by the HTTP specification. * * @return HTTP line as a string * @throws IOException if an I/O error occurs. */ private int lineFromLineBuffer(final CharArrayBuffer charbuffer) throws IOException { // discard LF if found int len = this.lineBuffer.length(); if (len > 0) { if (this.lineBuffer.byteAt(len - 1) == HTTP.LF) { len--; } // discard CR if found if (len > 0) { if (this.lineBuffer.byteAt(len - 1) == HTTP.CR) { len--; } } } if (this.ascii) { charbuffer.append(this.lineBuffer, 0, len); } else { final ByteBuffer bbuf = ByteBuffer.wrap(this.lineBuffer.buffer(), 0, len); len = appendDecoded(charbuffer, bbuf); } this.lineBuffer.clear(); return len; } private int lineFromReadBuffer(final CharArrayBuffer charbuffer, final int position) throws IOException { final int off = this.bufferPos; int i = position; this.bufferPos = i + 1; if (i > off && this.buffer[i - 1] == HTTP.CR) { // skip CR if found i--; } int len = i - off; if (this.ascii) { charbuffer.append(this.buffer, off, len); } else { final ByteBuffer bbuf = ByteBuffer.wrap(this.buffer, off, len); len = appendDecoded(charbuffer, bbuf); } return len; } private int appendDecoded( final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException { if (!bbuf.hasRemaining()) { return 0; } if (this.decoder == null) { this.decoder = this.charset.newDecoder(); this.decoder.onMalformedInput(this.onMalformedCharAction); this.decoder.onUnmappableCharacter(this.onUnmappableCharAction); } if (this.cbuf == null) { this.cbuf = CharBuffer.allocate(1024); } this.decoder.reset(); int len = 0; while (bbuf.hasRemaining()) { final CoderResult result = this.decoder.decode(bbuf, this.cbuf, true); len += handleDecodingResult(result, charbuffer, bbuf); } final CoderResult result = this.decoder.flush(this.cbuf); len += handleDecodingResult(result, charbuffer, bbuf); this.cbuf.clear(); return len; } private int handleDecodingResult( final CoderResult result, final CharArrayBuffer charbuffer, final ByteBuffer bbuf) throws IOException { if (result.isError()) { result.throwException(); } this.cbuf.flip(); final int len = this.cbuf.remaining(); while (this.cbuf.hasRemaining()) { charbuffer.append(this.cbuf.get()); } this.cbuf.compact(); return len; } @Override public String readLine() throws IOException { final CharArrayBuffer charbuffer = new CharArrayBuffer(64); final int readLen = readLine(charbuffer); if (readLen != -1) { return charbuffer.toString(); } return null; } @Override public HttpTransportMetrics getMetrics() { return this.metrics; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy