| /* |
| * $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) { |
| ; |
| } |
| } |
| |
| } |