com.maverick.http.HttpResponse.java Source code

Java tutorial

Introduction

Here is the source code for com.maverick.http.HttpResponse.java

Source

/*
*  Adito
*
*  Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
*
*  This program is free software; you can redistribute it and/or
*  modify it under the terms of the GNU General Public License
*  as published by the Free Software Foundation; either version 2 of
*  the License, or (at your option) any later version.
*  This program is distributed in the hope that it will be useful,
*  but WITHOUT ANY WARRANTY; without even the implied warranty of
*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
*  GNU General Public License for more details.
*
*  You should have received a copy of the GNU General Public
*  License along with this program; if not, write to the Free Software
*  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/

package com.maverick.http;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.NoSuchElementException;
import java.util.StringTokenizer;

/**
 * 
 * @author Lee David Painter <a href="mailto:lee@localhost">&lt;lee@localhost&gt;</a>
 */
public class HttpResponse extends HttpHeader {

    // #ifdef DEBUG
    static org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(HttpResponse.class);
    // #endif

    protected ByteArrayOutputStream chunked;
    protected String begin;

    private String version = ""; //$NON-NLS-1$
    private int status;
    private String reason = ""; //$NON-NLS-1$

    boolean closeConnection = false;
    int contentLength = 0;

    boolean foundContinue = false;
    boolean release = true;

    InputStream in = new ByteArrayInputStream(new byte[] {});

    HttpConnection con;
    HttpContinue cont;

    public HttpResponse(HttpConnection con) throws IOException {
        this(con, false);
    }

    public HttpResponse(HttpConnection con, HttpContinue cont) throws IOException {
        this.con = con;
        this.cont = cont;
        doResponse(false);
    }

    public HttpResponse(HttpConnection con, boolean headerOnly) throws IOException {
        this.con = con;
        doResponse(headerOnly);
    }

    private void doResponse(boolean headerOnly) throws IOException {

        do {

            begin = readLine(con.getInputStream());

            while (begin.trim().length() == 0) {
                begin = readLine(con.getInputStream());
            }

            // #ifdef DEBUG
            log.debug(MessageFormat.format(Messages.getString("HttpResponse.startLine"), new Object[] { begin })); //$NON-NLS-1$
            // #endif
            processResponse();

            if (status == 100) {
                if (cont != null)
                    cont.continueRequest(con);
                foundContinue = true;

                String tmp;
                while (true) {

                    /**
                     * Fix for IIS web servers, they are sending additional
                     * headers with the 100 Continue response.
                     */
                    tmp = readLine(con.getInputStream());

                    // #ifdef DEBUG
                    log.debug(MessageFormat.format(Messages.getString("HttpResponse.received100"), //$NON-NLS-1$
                            new Object[] { tmp }));
                    // #endif

                    if (tmp.equals("")) //$NON-NLS-1$
                        break;
                }

            }
        } while (status >= 100 && status < 200);

        processHeaderFields(con.getInputStream());

        if (!headerOnly) {

            /**
             * LDP - Moved transfer encoding check to here, seems the more
             * logical place to have it, plus I needed to work around the stream
             * being overidden by the Connection: close header.
             */
            if (getHeaderField("transfer-encoding") != null) { //$NON-NLS-1$
                if (getHeaderField("transfer-encoding").equalsIgnoreCase( //$NON-NLS-1$
                        "chunked")) { //$NON-NLS-1$
                    in = new ChunkedInputStream(con.getInputStream());
                    // Remove the transfer-encoding header
                    removeFields("transfer-encoding"); //$NON-NLS-1$
                }
            } else if (getHeaderField("Content-Length") != null) { //$NON-NLS-1$
                contentLength = Integer.parseInt(getHeaderField("Content-Length")); //$NON-NLS-1$
                in = new ContentInputStream(contentLength);
            } else if (getHeaderField("Connection") != null && //$NON-NLS-1$
                    getHeaderField("Connection").equalsIgnoreCase("close")) { //$NON-NLS-1$ //$NON-NLS-2$

                // Set the connection as unusable by others
                con.canReuse = false;

                /**
                 * LDP - Since the connection is being closed we could have some
                 * content and no content-length header so just make this
                 * responses inputstream the connections inputstream, it will go
                 * EOF once drained.
                 */
                in = con.getInputStream();
            } else if (getHeaderField("Proxy-Connection") != null && //$NON-NLS-1$
                    getHeaderField("Proxy-Connection").equalsIgnoreCase("close")) { //$NON-NLS-1$ //$NON-NLS-2$

                // Set the connection as unusable by others
                con.canReuse = false;

                /**
                 * LDP - Since the connection is being closed we could have some
                 * content and no content-length header so just make this
                 * responses inputstream the connections inputstream, it will go
                 * EOF once drained.
                 */
                in = con.getInputStream();
            } else {
                /**
                 * No data to read so return an empty stream.
                 */
                in = new ByteArrayInputStream(new byte[] {});
            }
        }

        /**
         * Finally check the connection close status again to set the canReuse
         * flag on the HttpConnection.
         */
        if (getHeaderField("Connection") != null && //$NON-NLS-1$
                getHeaderField("Connection").equalsIgnoreCase("close")) { //$NON-NLS-1$ //$NON-NLS-2$

            // Set the connection as unusable by others
            con.canReuse = false;

        } else if (getHeaderField("Proxy-Connection") != null && //$NON-NLS-1$
                getHeaderField("Proxy-Connection").equalsIgnoreCase("close")) { //$NON-NLS-1$ //$NON-NLS-2$

            // Set the connection as unusable by others
            con.canReuse = false;
        }

    }

    public void close() {
        close(true);
    }

    public boolean hasContinue() {
        return foundContinue;
    }

    public synchronized void close(boolean release) {

        /**
         * LDP - I've added this release flag because the doAuthentication
         * method of HttpClient was calling close to drain the connection so
         * that it could be reused. This was correct but the connection was
         * prematurely being put back into the connection manager, and was being
         * reused by other threads. Since we want to keep hold of the existing
         * connection, in case of NTLM authentication we want to drain but not
         * release.
         */
        if (con == null)
            return;

        try {
            if (in instanceof ChunkedInputStream) {
                ((ChunkedInputStream) in).drain();
            } else if (in instanceof ContentInputStream) {
                ((ContentInputStream) in).drain();
            }
        } catch (IOException ex) {
            // Exception during skip better close this connection
            con.canReuse = false;
        } finally {
            if (release) {
                con.release();
                con = null;
            }
        }
    }

    public String getContentType() {
        return getHeaderField("Content-Type");
    }

    public String getContentTypeWithoutParameter() {
        String contentType = getContentType();
        if (contentType == null) {
            return null;
        }
        int idx = contentType.indexOf(';');
        return idx == -1 ? contentType : contentType.substring(0, idx);
    }

    protected String readLine(InputStream in) throws IOException {
        StringBuffer lineBuf = new StringBuffer();
        int c;

        while (true) {
            c = in.read();

            if (c == -1) {
                if (lineBuf.length() == 0)
                    throw new EOFException(Messages.getString("HttpResponse.unexpectedEOF")); //$NON-NLS-1$

                break;
            }

            if (c != '\n') {
                lineBuf.append((char) c);
            } else {
                break;
            }
        }

        return lineBuf.toString().trim();
    }

    public String getStartLine() {
        return begin;
    }

    protected void processHeaderFields(InputStream in) throws IOException {
        clearHeaderFields();

        StringBuffer lineBuf = new StringBuffer();
        String lastHeaderName = null;
        int c;

        while (true) {
            c = in.read();

            if (c == -1) {
                throw new IOException(Messages.getString("HttpResponse.headerCorrupt")); //$NON-NLS-1$
            }

            if (c != '\n') {
                lineBuf.append((char) c);
            } else {
                String line = lineBuf.toString().trim();
                lineBuf.setLength(0);
                if (line.length() != 0) {
                    lastHeaderName = processNextLine(line, lastHeaderName);
                } else {
                    break;
                }
            }
        }
    }

    private String processNextLine(String line, String lastHeaderName) throws IOException {
        String name;
        String value;
        char c = line.charAt(0);

        if ((c == ' ') || (c == '\t')) {
            name = lastHeaderName;
            value = getHeaderField(lastHeaderName) + " " + line.trim(); //$NON-NLS-1$
        } else {
            int n = line.indexOf(':');

            if (n == -1) {
                throw new IOException(MessageFormat.format(Messages.getString("HttpResponse.corruptField"), //$NON-NLS-1$
                        new Object[] { line })); //$NON-NLS-2$
            }

            name = line.substring(0, n);
            value = line.substring(n + 1).trim();
        }

        // #ifdef DEBUG
        log.debug(MessageFormat.format(Messages.getString("HttpResponse.receivedHeader"), //$NON-NLS-1$
                new Object[] { name, value }));
        // #endif
        addHeaderField(name, value);

        return name;
    }

    public InputStream getInputStream() {
        return in;
    }

    public HttpConnection getConnection() {
        return con;
    }

    public String getVersion() {
        return version;
    }

    public int getStatus() {
        return status;
    }

    public String getReason() {
        return reason;
    }

    private void processResponse() throws IOException {
        StringTokenizer tokens = new StringTokenizer(begin, WHITE_SPACE, false);
        reason = ""; //$NON-NLS-1$
        try {
            version = tokens.nextToken();
            status = Integer.parseInt(tokens.nextToken());
            while (tokens.hasMoreTokens()) {
                reason += tokens.nextToken() + " "; //$NON-NLS-1$
            }
            reason = reason.trim();
        } catch (NoSuchElementException e) {
            throw new IOException(MessageFormat.format(Messages.getString("HttpResponse.failedToReadResponse"), //$NON-NLS-1$
                    new Object[] { begin }));
        } catch (NumberFormatException e) {
            throw new IOException(MessageFormat.format(Messages.getString("HttpResponse.failedToReadResponse"), //$NON-NLS-1$
                    new Object[] { begin }));
        }
    }

    /**
     * We will use this stream to return data that is encoded using the
     * "Transfer-Encoding: chunked" header.
     */
    class ChunkedInputStream extends InputStream {

        long chunkLength;
        InputStream in;

        ChunkedInputStream(InputStream in) throws IOException {

            this.in = in;
            // Read from the InputStream until we receive chunk size of zero
            chunkLength = Long.parseLong(readLine(in), 16);
        }

        public int read() throws IOException {
            byte[] b = new byte[1];
            int read = read(b, 0, 1);
            if (read == -1)
                return -1;
            else
                return b[0] & 0xFF;
        }

        public void drain() throws IOException {
            long len;
            byte[] buf = new byte[65535];
            while (contentLength > 0) {
                len = con.getInputStream().read(buf, 0, buf.length);

                if (contentLength > 0)
                    contentLength -= len;
                else
                    break;
            }
        }

        public synchronized int read(byte[] buf, int off, int len) throws IOException {

            if (chunkLength == 0 || con == null) {
                return -1;
            }
            int read;
            int count = 0;
            while (len > 0 && chunkLength > 0) {

                read = in.read(buf, off, (int) (len > chunkLength ? chunkLength : len));

                if (read == -1)
                    throw new EOFException(Messages.getString("HttpResponse.unexpectedEOFDuringChunking")); //$NON-NLS-1$

                chunkLength -= read;
                len -= read;
                off += read;
                count += read;

                if (chunkLength == 0) {
                    readLine(in);
                    chunkLength = Long.parseLong(readLine(in), 16);
                    if (chunkLength == 0)
                        close();
                }
            }

            return count;
        }

        public synchronized void close() throws IOException {

            if (con != null) {
                readLine(in);
                HttpResponse.this.close(true);
            }
        }
    }

    /**
     * We will use this to return standard content
     */
    class ContentInputStream extends InputStream {

        long contentLength;

        ContentInputStream(long contentLength) {
            this.contentLength = contentLength;
        }

        public synchronized int available() {
            return (int) contentLength;
        }

        public synchronized long skip(long length) throws IOException {
            return con.getInputStream().skip(length);
        }

        public void drain() throws IOException {
            long len;

            while (contentLength > 0) {
                len = con.getInputStream().skip(contentLength);

                if (contentLength > 0)
                    contentLength -= len;
                else
                    break;
            }
        }

        public synchronized int read() throws IOException {

            if (contentLength == 0 || con == null) {
                return -1;
            } else {
                int b = con.getInputStream().read();
                if (b == -1)
                    throw new EOFException(
                            MessageFormat.format(Messages.getString("HttpResponse.unexpectedEOFInResponseExpected"), //$NON-NLS-1$
                                    new Object[] { new Long(contentLength) }));
                contentLength--;

                if (contentLength == 0)
                    close();

                return b;
            }
        }

        public synchronized int read(byte[] buf, int off, int len) throws IOException {
            if (contentLength == 0 || con == null) {
                return -1;
            } else {
                int read = con.getInputStream().read(buf, off, (contentLength > len ? len : (int) contentLength));
                if (read == -1)
                    throw new EOFException(
                            MessageFormat.format(Messages.getString("HttpResponse.unexpectedEOFInResponseExpected"), //$NON-NLS-1$
                                    new Object[] { new Long(contentLength) })); //$NON-NLS-2$
                contentLength -= read;

                if (contentLength == 0)
                    close();

                return read;
            }

        }

        public synchronized void close() {
            // Release the connection back to the client pool
            if (con != null)
                HttpResponse.this.close(true);
        }
    }

}