Java tutorial
/* * 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"><lee@localhost></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); } } }