diff options
Diffstat (limited to 'src/org/apache/http/impl/io/ChunkedInputStream.java')
-rw-r--r-- | src/org/apache/http/impl/io/ChunkedInputStream.java | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/src/org/apache/http/impl/io/ChunkedInputStream.java b/src/org/apache/http/impl/io/ChunkedInputStream.java new file mode 100644 index 0000000..60cae90 --- /dev/null +++ b/src/org/apache/http/impl/io/ChunkedInputStream.java @@ -0,0 +1,294 @@ +/* + * $HeadURL: http://svn.apache.org/repos/asf/httpcomponents/httpcore/trunk/module-main/src/main/java/org/apache/http/impl/io/ChunkedInputStream.java $ + * $Revision: 569843 $ + * $Date: 2007-08-26 10:05:40 -0700 (Sun, 26 Aug 2007) $ + * + * ==================================================================== + * 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 org.apache.http.Header; +import org.apache.http.HttpException; +import org.apache.http.MalformedChunkCodingException; +import org.apache.http.io.SessionInputBuffer; +import org.apache.http.protocol.HTTP; +import org.apache.http.util.CharArrayBuffer; +import org.apache.http.util.ExceptionUtils; + +/** + * Implements chunked transfer coding. + * See <a href="http://www.w3.org/Protocols/rfc2616/rfc2616.txt">RFC 2616</a>, + * <a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6">section 3.6.1</a>. + * It transparently coalesces chunks of a HTTP stream that uses chunked + * transfer coding. After the stream is read to the end, it provides access + * to the trailers, if any. + * <p> + * Note that this class NEVER closes the underlying stream, even when close + * gets called. Instead, it will read until the "end" of its chunking on + * close, which allows for the seamless execution of subsequent HTTP 1.1 + * requests, while not requiring the client to remember to read the entire + * contents of the response. + * </p> + * + * @author Ortwin Glueck + * @author Sean C. Sullivan + * @author Martin Elwin + * @author Eric Johnson + * @author <a href="mailto:mbowler@GargoyleSoftware.com">Mike Bowler</a> + * @author Michael Becke + * @author <a href="mailto:oleg at ural.ru">Oleg Kalnichevski</a> + * + * @since 4.0 + * + */ +public class ChunkedInputStream extends InputStream { + + /** The session input buffer */ + private SessionInputBuffer in; + + private final CharArrayBuffer buffer; + + /** The chunk size */ + private int chunkSize; + + /** The current position within the current chunk */ + private int pos; + + /** True if we'are at the beginning of stream */ + private boolean bof = true; + + /** True if we've reached the end of stream */ + private boolean eof = false; + + /** True if this stream is closed */ + private boolean closed = false; + + private Header[] footers = new Header[] {}; + + public ChunkedInputStream(final SessionInputBuffer in) { + super(); + if (in == null) { + throw new IllegalArgumentException("Session input buffer may not be null"); + } + this.in = in; + this.pos = 0; + this.buffer = new CharArrayBuffer(16); + } + + /** + * <p> Returns all the data in a chunked stream in coalesced form. A chunk + * is followed by a CRLF. The method returns -1 as soon as a chunksize of 0 + * is detected.</p> + * + * <p> Trailer headers are read automcatically at the end of the stream and + * can be obtained with the getResponseFooters() method.</p> + * + * @return -1 of the end of the stream has been reached or the next data + * byte + * @throws IOException If an IO problem occurs + */ + public int read() throws IOException { + if (this.closed) { + throw new IOException("Attempted read from closed stream."); + } + if (this.eof) { + return -1; + } + if (this.pos >= this.chunkSize) { + nextChunk(); + if (this.eof) { + return -1; + } + } + pos++; + return in.read(); + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @param off The offset into the byte array at which bytes will start to be + * placed. + * @param len the maximum number of bytes that can be returned. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[], int, int) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b, int off, int len) throws IOException { + + if (closed) { + throw new IOException("Attempted read from closed stream."); + } + + if (eof) { + return -1; + } + if (pos >= chunkSize) { + nextChunk(); + if (eof) { + return -1; + } + } + len = Math.min(len, chunkSize - pos); + int count = in.read(b, off, len); + pos += count; + return count; + } + + /** + * Read some bytes from the stream. + * @param b The byte array that will hold the contents from the stream. + * @return The number of bytes returned or -1 if the end of stream has been + * reached. + * @see java.io.InputStream#read(byte[]) + * @throws IOException if an IO problem occurs. + */ + public int read (byte[] b) throws IOException { + return read(b, 0, b.length); + } + + /** + * Read the next chunk. + * @throws IOException If an IO error occurs. + */ + private void nextChunk() throws IOException { + chunkSize = getChunkSize(); + if (chunkSize < 0) { + throw new MalformedChunkCodingException("Negative chunk size"); + } + bof = false; + pos = 0; + if (chunkSize == 0) { + eof = true; + parseTrailerHeaders(); + } + } + + /** + * Expects the stream to start with a chunksize in hex with optional + * comments after a semicolon. The line must end with a CRLF: "a3; some + * comment\r\n" Positions the stream at the start of the next line. + * + * @param in The new input stream. + * @param required <tt>true<tt/> if a valid chunk must be present, + * <tt>false<tt/> otherwise. + * + * @return the chunk size as integer + * + * @throws IOException when the chunk size could not be parsed + */ + private int getChunkSize() throws IOException { + // skip CRLF + if (!bof) { + int cr = in.read(); + int lf = in.read(); + if ((cr != HTTP.CR) || (lf != HTTP.LF)) { + throw new MalformedChunkCodingException( + "CRLF expected at end of chunk"); + } + } + //parse data + this.buffer.clear(); + int i = this.in.readLine(this.buffer); + if (i == -1) { + throw new MalformedChunkCodingException( + "Chunked stream ended unexpectedly"); + } + int separator = this.buffer.indexOf(';'); + if (separator < 0) { + separator = this.buffer.length(); + } + try { + return Integer.parseInt(this.buffer.substringTrimmed(0, separator), 16); + } catch (NumberFormatException e) { + throw new MalformedChunkCodingException("Bad chunk header"); + } + } + + /** + * Reads and stores the Trailer headers. + * @throws IOException If an IO problem occurs + */ + private void parseTrailerHeaders() throws IOException { + try { + this.footers = AbstractMessageParser.parseHeaders + (in, -1, -1, null); + } catch (HttpException e) { + IOException ioe = new MalformedChunkCodingException("Invalid footer: " + + e.getMessage()); + ExceptionUtils.initCause(ioe, e); + throw ioe; + } + } + + /** + * Upon close, this reads the remainder of the chunked message, + * leaving the underlying socket at a position to start reading the + * next response without scanning. + * @throws IOException If an IO problem occurs. + */ + public void close() throws IOException { + if (!closed) { + try { + if (!eof) { + exhaustInputStream(this); + } + } finally { + eof = true; + closed = true; + } + } + } + + public Header[] getFooters() { + return (Header[])this.footers.clone(); + } + + /** + * Exhaust an input stream, reading until EOF has been encountered. + * + * <p>Note that this function is intended as a non-public utility. + * This is a little weird, but it seemed silly to make a utility + * class for this one function, so instead it is just static and + * shared that way.</p> + * + * @param inStream The {@link InputStream} to exhaust. + * @throws IOException If an IO problem occurs + */ + static void exhaustInputStream(final InputStream inStream) throws IOException { + // read and discard the remainder of the message + byte buffer[] = new byte[1024]; + while (inStream.read(buffer) >= 0) { + ; + } + } + +} |