Open Source Repository

Home /commons-httpclient/commons-httpclient-2.0 | Repository Home



org/apache/commons/httpclient/ResponseInputStream.java
/*
 * $Header: /home/cvs/jakarta-commons/httpclient/src/java/org/apache/commons/httpclient/ResponseInputStream.java,v 1.23 2003/02/12 13:21:27 olegk Exp $
 * $Revision: 1.23 $
 * $Date: 2003/02/12 13:21:27 $
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999-2003 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Commons", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact [email protected].
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * 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/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.commons.httpclient;

import java.io.IOException;
import java.io.InputStream;

import org.apache.commons.logging.LogFactory;
import org.apache.commons.logging.Log;

/**
 <p>{@link InputStream} wrapper supporting the chunked transfer encoding.</p>
 *
 @author <a href="mailto:[email protected]">Remy Maucherat</a>
 @author Sean C. Sullivan
 @author <a href="mailto:[email protected]">dIon Gillard</a>
 @author <a href="mailto:[email protected]">Mike Bowler</a>
 @version $Revision: 1.23 $ $Date: 2003/02/12 13:21:27 $
 *
 @deprecated Use new ChunkedInputStream(HttpConnecion#getResponseInputStream());
 *
 */
public class ResponseInputStream extends InputStream {

    // -------------------------------------------------------- Class Variables
    
    /** Log object for this class. */
    public static final Log LOG = LogFactory.getLog(ResponseInputStream.class);

    // ----------------------------------------------------------- Constructors

    /**
     *
     @param stream Must be non-null.
     @param chunked <code>true</code> if the input stream is chunked
     @param contentLength content length
     *
     @deprecated Use ChunkedInputStream;
     */
    public ResponseInputStream(InputStream stream, boolean chunked, int contentLength) {
        LOG.trace("enter ResponseInputStream(InputStream, boolean, int)");

        if (null == stream) {
            throw new NullPointerException("InputStream parameter is null");
        }
        closed = false;
        count = 0;
        this.chunk = chunked;
        this.contentLength = contentLength;
        this.stream = stream;
    }

    /**
     * Construct a servlet input stream associated with the specified Request.
     *
     @param stream Must be non-null.
     @param method Must be non-null.
     *
     @deprecated Use ChunkedInputStream;
     *
     */
    public ResponseInputStream(InputStream stream, HttpMethod method) {
        super();
        LOG.trace("enter ResponseInputStream(InputStream, HttpMethod)");

        if (null == stream) {
            throw new NullPointerException("InputStream parameter is null");
        }

        if (null == method) {
            throw new NullPointerException("HttpMethod parameter is null");
        }

        closed = false;
        count = 0;

        // Retrieving transfer encoding header
        Header transferEncoding = method.getResponseHeader("transfer-encoding");
        if ((null != transferEncoding&& (transferEncoding.getValue().
                             toLowerCase().indexOf("chunked"!= -1)) {
            chunk = true;
        }

        // Retrieving content length header
        Header contentLengthHeader = method.getResponseHeader("content-length");
        if (null != contentLengthHeader) {
            try {
                this.contentLength =
                    Integer.parseInt(contentLengthHeader.getValue());
            catch (NumberFormatException ignored) {
                   // we are intentionally ignoring the NumberFormatException
            }
        }

        this.stream = stream;
    }


    // ----------------------------------------------------- Instance Variables

    /**
     * Has this stream been closed?
     */
    private boolean closed = false;

    /**
     * Use chunking ?
     */
    private boolean chunk = false;

    /**
     * True if the final chunk was found.
     */
    private boolean endChunk = false;

    /**
     * Chunk buffer.
     */
    private byte[] buffer = null;

    /**
     * Current chunk length.
     */
    private int length = 0;

    /**
     * Current chunk buffer position.
     */
    private int pos = 0;

    /**
     * The number of bytes which have already been returned by this stream.
     */
    private int count = 0;

    /**
     * The content length past which we will not read, or -1 if there is
     * no defined content length.
     */
    private int contentLength = -1;

    /**
     * The underlying input stream from which we should read data.
     */
    private InputStream stream = null;

    // --------------------------------------------------------- Public Methods

    /**
     * Close this input stream.  No physical level I-O is performed, but
     * any further attempt to read from this stream will throw an IOException.
     * If a content length has been set but not all of the bytes have yet been
     * consumed, the remaining bytes will be swallowed.
     
     @throws IOException If an IO problem occurs.
     *
     @deprecated Use ChunkedInputStream;
     */
    public void close() throws IOException {
        LOG.trace("enter ResponseInputStream.close()");
    /*
        // Xerces appears to doubly-close the input stream...
        if(closed) {
            throw new IOException("Stream is already closed");
        }
    */

        //TODO: This close code is not very robust
        if (!closed) {
            try {
                if (chunk) {
                    while (!endChunk) {
                        int b = read();
                        if (b < 0) {
                            break;
                        }
                    }
                else {
                    if (length > 0) {
                        while (count < length) {
                            int b = read();
                            if (b < 0) {
                                break;
                            }
                        }
                    }
                }
            catch (java.io.IOException ex) {
                throw ex;
            finally {
                    closed = true;
            }
        }
    }


    /**
     * Read up to <code>len</code> bytes of data from the input stream
     * into an array of bytes.  An attempt is made to read as many as
     <code>len</code> bytes, but a smaller number may be read,
     * possibly zero.  The number of bytes actually read is returned as
     * an integer.  This method blocks until input data is available,
     * end of file is detected, or an exception is thrown.
     *
     @param b The buffer into which the data is read
     @param off The start offset into array <code>b</code> at which
     *  the data is written
     @param len The maximum number of bytes to read
     @return The number of bytes that were read.
     *
     @exception IOException if an input/output error occurs
     *
     @deprecated Use ChunkedInputStream;
     */
    public int read(byte b[]int off, int len)
    throws IOException {
        LOG.trace("enter ResponseInputStream.read(byte, int, int)");

        int avail = length - pos;
        if ((avail == 0&& (!fillBuffer())) {
            return (-1);
        }

        avail = length - pos;
        if (avail == 0) {
            return (-1);
        }

        int toCopy = avail;

        if (toCopy < 0) {
          return (-1);
        }

        if (avail > len) {
            toCopy = len;
        }
        System.arraycopy(buffer, pos, b, off, toCopy);
        pos += toCopy;
        return toCopy;
    }

    /**
     * Read and return a single byte from this input stream, or -1 if end of
     * file has been encountered.
     *
     @return The next byte in the stream or -1.
     @exception IOException if an input/output error occurs
     *
     @deprecated Use ChunkedInputStream;
     */
    public int read() throws IOException {
        LOG.trace("enter ResponseInputStream.read()");

        if (pos == length) {
            if (!fillBuffer()) {
                return (-1);
            }
        }

        return (buffer[pos++0xff);

    }

    // -------------------------------------------------------- Private Methods


    /**
     * Fill the chunk buffer.
     @return true If successful
     @throws IOException If an IO problem occurs.
     *
     @deprecated Use ChunkedInputStream;
     */
    private boolean fillBuffer() throws IOException {
        LOG.trace("enter ResponseInputStream.fillBuffer()");

        // Has this stream been closed?
        if (closed) {
            return false;
        }
            //throw new IOException("Stream is closed");

        if (endChunk) {
            return false;
        }

        // Have we read the specified content length already?
        if ((contentLength >= 0&& (count >= contentLength)) {
            return false;    // End of file indicator
        }

        pos = 0;

        if (chunk) {

            try {
                String numberValue = readLineFromStream();
                if (numberValue == null) {
                  throw new NumberFormatException("unable to find chunk length");
                }

                length = Integer.parseInt(numberValue.trim()16);
            catch (NumberFormatException e) {
                // Critical error, unable to parse the chunk length
                length = -1;
                chunk = false;
                endChunk = true;
                closed = true;
                return false;
            }

            if (length == 0) {

                // Skipping trailing headers, if any
                String trailingLine = readLineFromStream();
                while (!trailingLine.equals("")) {
                    trailingLine = readLineFromStream();
                }
                endChunk = true;
                return false;

            else {

                if ((buffer == null|| (length > buffer.length)) {
                    buffer = new byte[length];
                }

                // Now read the whole chunk into the buffer

                int nbRead = 0;
                int currentRead = 0;

                while (nbRead < length) {
                    try {
                        currentRead = stream.read(buffer, nbRead,
                                        length - nbRead);
                    catch (Throwable t) {
                        LOG.debug("Exception thrown reading chunk from response", t);
                        throw new IOException();
                    }
                    if (currentRead < 0) {
                        throw new IOException("Not enough bytes read");
                    }
                    nbRead += currentRead;
                }

                // Skipping the CRLF
                readLineFromStream();

            }

        else //not using chunking

            try {
                if (buffer == null) {
                    buffer = new byte[4096];
                }
                length = stream.read(buffer);
                count += length;
            catch (Throwable t) {
                LOG.debug("Exception thrown reading from response", t);
                throw new IOException(t.getMessage());
            }

        }

        return true;

    }

    /**
     * Reads the input stream, one line at a time. Reads bytes into an array,
     * until it reads a certain number of bytes or reaches a newline character,
     * which it reads into the array as well.
     *
     @return The line that was read, or <code>null</code> if end-of-file
     *  was encountered
     @exception IOException   if an input or output exception has occurred
     *
     @deprecated Use ChunkedInputStream;
     */
    private String readLineFromStream()
    throws IOException {
        LOG.trace("enter ResponseInputStream.ReadLineFromStream()");

        StringBuffer sb = new StringBuffer();
        while (true) {
            int ch = stream.read();
            if (ch < 0) {
                if (sb.length() == 0) {
                    return (null);
                else {
                    break;
                }
            else if (ch == '\r') {
                continue;
            else if (ch == '\n') {
                break;
            }
            sb.append((charch);
        }
        return (sb.toString());
    }
}