/*
* ====================================================================
* 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
* <http://www.apache.org/>.
*
*/
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.
* <p>
* {@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 instream the source input stream.
* @param buffersize the size of the internal buffer.
* @param params HTTP parameters.
*/
protected void
init(final
InputStream instream, final int
buffersize, final
HttpParams params) {
Args.
notNull(
instream, "Input stream");
Args.
notNegative(
buffersize, "Buffer size");
Args.
notNull(
params, "HTTP parameters");
this.
instream =
instream;
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
l;
final int
off = this.
bufferlen;
final int
len = this.
buffer.length -
off;
l = this.
instream.
read(this.
buffer,
off,
len);
if (
l == -1) {
return -1;
} else {
this.
bufferlen =
off +
l;
this.
metrics.
incrementBytesTransferred(
l);
return
l;
}
}
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;
} else {
// 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.
* <p>
* 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.
* <p>
* 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
l =
readLine(
charbuffer);
if (
l != -1) {
return
charbuffer.
toString();
} else {
return null;
}
}
@
Override
public
HttpTransportMetrics getMetrics() {
return this.
metrics;
}
}