/* * @(#)HTTPResponse.java 0.3-3 06/05/2001 * * This file is part of the HTTPClient package * Copyright (C) 1996-2001 Ronald Tschalär * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * This library 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free * Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, * MA 02111-1307, USA * * For questions, suggestions, bug-reports, enhancement-requests etc. * I may be contacted at: * * ronald@innovation.ch * * The HTTPClient's home page is located at: * * http://www.innovation.ch/java/HTTPClient/ * */ package HTTPClient; import java.io.IOException; import java.io.InterruptedIOException; import java.io.InputStream; import java.io.ByteArrayInputStream; import java.net.URL; import java.util.Date; import java.util.Enumeration; /** * This defines the http-response class returned by the requests. It's * basically a wrapper around the Response class which first lets all * the modules handle the response before finally giving the info to * the user. * * @version 0.3-3 06/05/2001 * @author Ronald Tschalär * @since 0.3 */ public class HTTPResponse implements HTTPClientModuleConstants { /** the list of modules */ private HTTPClientModule[] modules; /** the timeout for reads */ private int timeout; /** the request */ private Request request = null; /** the current response */ Response response = null; /** the HttpOutputStream to synchronize on */ private HttpOutputStream out_stream = null; /** our input stream from the stream demux */ private InputStream inp_stream; /** the status code returned. */ private int StatusCode; /** the reason line associated with the status code. */ private String ReasonLine; /** the HTTP version of the response. */ private String Version; /** the original URI used. */ private URI OriginalURI = null; /** the final URI of the document. */ private URI EffectiveURI = null; /** any headers which were received and do not fit in the above list. */ private CIHashtable Headers = null; /** any trailers which were received and do not fit in the above list. */ private CIHashtable Trailers = null; /** the ContentLength of the data. */ private int ContentLength = -1; /** the data (body) returned. */ private byte[] Data = null; /** signals if we have got and parsed the headers yet? */ private boolean initialized = false; /** signals if we have got the trailers yet? */ private boolean got_trailers = false; /** marks this response as aborted (stop() in HTTPConnection) */ private boolean aborted = false; /** should the request be retried by the application? */ private boolean retry = false; /** the method used in the request */ private String method = null; // Constructors /** * Creates a new HTTPResponse. * * @param modules the list of modules handling this response * @param timeout the timeout to be used on stream read()'s */ HTTPResponse(HTTPClientModule[] modules, int timeout, Request orig) { this.modules = modules; this.timeout = timeout; try { int qp = orig.getRequestURI().indexOf('?'); this.OriginalURI = new URI(orig.getConnection().getProtocol(), null, orig.getConnection().getHost(), orig.getConnection().getPort(), qp < 0 ? orig.getRequestURI() : orig.getRequestURI().substring(0, qp), qp < 0 ? null : orig.getRequestURI().substring(qp+1), null); } catch (ParseException pe) { } this.method = orig.getMethod(); } /** * @param req the request * @param resp the response */ void set(Request req, Response resp) { this.request = req; this.response = resp; resp.http_resp = this; resp.timeout = timeout; this.aborted = resp.final_resp; } /** * @param req the request * @param resp the response */ void set(Request req, HttpOutputStream out_stream) { this.request = req; this.out_stream = out_stream; } // Methods /** * Give the status code for this request. These are grouped as follows: *
getHeader("Server")
instead.
* @see #getHeader(java.lang.String)
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public final String getServer() throws IOException, ModuleException
{
if (!initialized) handleResponse();
return getHeader("Server");
}
/**
* Get the original URI used in the request.
*
* @return the URI used in primary request
*/
public final URI getOriginalURI()
{
return OriginalURI;
}
/**
* Get the final URL of the document. This is set if the original
* request was deferred via the "moved" (301, 302, or 303) return
* status.
*
* @return the effective URL, or null if no redirection occured
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
* @deprecated use getEffectiveURI() instead
* @see #getEffectiveURI
*/
public final URL getEffectiveURL() throws IOException, ModuleException
{
if (!initialized) handleResponse();
if (EffectiveURI != null)
return EffectiveURI.toURL();
return null;
}
/**
* Get the final URI of the document. If the request was redirected
* via the "moved" (301, 302, 303, or 307) return status this returns
* the URI used in the last redirection; otherwise it returns the
* original URI.
*
* @return the effective URI
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public final URI getEffectiveURI() throws IOException, ModuleException
{
if (!initialized) handleResponse();
if (EffectiveURI != null)
return EffectiveURI;
return OriginalURI;
}
/**
* Retrieves the value for a given header.
*
* @param hdr the header name.
* @return the value for the header, or null if non-existent.
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public String getHeader(String hdr) throws IOException, ModuleException
{
if (!initialized) handleResponse();
return (String) Headers.get(hdr.trim());
}
/**
* Retrieves the value for a given header. The value is parsed as an
* int.
*
* @param hdr the header name.
* @return the value for the header if the header exists
* @exception NumberFormatException if the header's value is not a number
* or if the header does not exist.
* @exception IOException if any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public int getHeaderAsInt(String hdr)
throws IOException, ModuleException, NumberFormatException
{
String val = getHeader(hdr);
if (val == null)
throw new NumberFormatException("null");
return Integer.parseInt(val);
}
/**
* Retrieves the value for a given header. The value is parsed as a
* date; if this fails it is parsed as a long representing the number
* of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
* exception is thrown.
* getData()
to force the data to be read.
*
* @param trailer the trailer name.
* @return the value for the trailer, or null if non-existent.
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
* @see #getData()
*/
public String getTrailer(String trailer) throws IOException, ModuleException
{
if (!got_trailers) getTrailers();
return (String) Trailers.get(trailer.trim());
}
/**
* Retrieves the value for a given tailer. The value is parsed as an
* int.
*
* @param trailer the tailer name.
* @return the value for the trailer if the trailer exists
* @exception NumberFormatException if the trailer's value is not a number
* or if the trailer does not exist.
* @exception IOException if any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public int getTrailerAsInt(String trailer)
throws IOException, ModuleException, NumberFormatException
{
String val = getTrailer(trailer);
if (val == null)
throw new NumberFormatException("null");
return Integer.parseInt(val);
}
/**
* Retrieves the value for a given trailer. The value is parsed as a
* date; if this fails it is parsed as a long representing the number
* of seconds since 12:00 AM, Jan 1st, 1970. If this also fails an
* IllegalArgumentException is thrown.
* getInputStream()
had been previously invoked
* then this method only returns any unread data remaining on the stream
* and then closes it.
*
* Note to the unwary: code like *
* System.out.println("The data: " + resp.getData()) ** will probably not do what you want - use *
* System.out.println("The data: " + resp.getText()) ** instead. * * @see #getInputStream() * @return an array containing the data (body) returned. If no data * was returned then it's set to a zero-length array. * @exception IOException If any io exception occured while reading * the data * @exception ModuleException if any module encounters an exception. */ public synchronized byte[] getData() throws IOException, ModuleException { if (!initialized) handleResponse(); if (Data == null) { try { readResponseData(inp_stream); } catch (InterruptedIOException ie) // don't intercept { throw ie; } catch (IOException ioe) { Log.write(Log.RESP, "HResp: (\"" + method + " " + OriginalURI.getPathAndQuery() + "\")"); Log.write(Log.RESP, " ", ioe); try { inp_stream.close(); } catch (Exception e) { } throw ioe; } inp_stream.close(); } return Data; } /** * Reads all the response data into a buffer and turns it into a string * using the appropriate character converter. Since this uses {@link * #getData() getData()}, the caveats of that method apply here as well. * * @see #getData() * @return the body as a String. If no data was returned then an empty * string is returned. * @exception IOException If any io exception occured while reading * the data, or if the content is not text * @exception ModuleException if any module encounters an exception. * @exception ParseException if an error occured trying to parse the * content-type header field */ public synchronized String getText() throws IOException, ModuleException, ParseException { String ct = getHeader("Content-Type"); if (ct == null || !ct.toLowerCase().startsWith("text/")) throw new IOException("Content-Type `" + ct + "' is not a text type"); String charset = Util.getParameter("charset", ct); if (charset == null) charset = "ISO-8859-1"; return new String(getData(), charset); } /** * Gets an input stream from which the returned data can be read. Note * that if
getData()
had been previously invoked it will
* actually return a ByteArrayInputStream created from that data.
*
* @see #getData()
* @return the InputStream.
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public synchronized InputStream getInputStream()
throws IOException, ModuleException
{
if (!initialized) handleResponse();
if (Data == null)
return inp_stream;
else
{
getData(); // ensure complete data is read
return new ByteArrayInputStream(Data);
}
}
/**
* Should the request be retried by the application? If the application
* used an HttpOutputStream in the request then various
* modules (such as the redirection and authorization modules) are not
* able to resend the request themselves. Instead, it becomes the
* application's responsibility. The application can check this flag, and
* if it's set, resend the exact same request. The modules such as the
* RedirectionModule or AuthorizationModule will then recognize the resend
* and fix up or redirect the request as required (i.e. they defer their
* normal action until the resend).
*
* If the application resends the request then it must * use the same HttpOutputStream instance. This is because the * modules use this to recognize the retried request and to perform the * necessary work on the request before it's sent. * *
Here is a skeleton example of usage: *
* OutputStream out = new HttpOutputStream(1234); * do * { * rsp = con.Post("/cgi-bin/my_cgi", out); * out.write(...); * out.close(); * } while (rsp.retryRequest()); * * if (rsp.getStatusCode() >= 300) * ... ** *
Note that for this to ever return true, the java system property
* HTTPClient.deferStreamed must be set to true at the beginning
* of the application (before the HTTPConnection class is loaded). This
* prevents unwary applications from causing inadvertent memory leaks. If
* an application does set this, then it must resend any request
* whose response returns true here in order to prevent memory leaks (a
* switch to JDK 1.2 will allow us to use weak references and eliminate
* this problem).
*
* @return true if the request should be retried.
* @exception IOException If any exception occurs on the socket.
* @exception ModuleException if any module encounters an exception.
*/
public boolean retryRequest() throws IOException, ModuleException
{
if (!initialized)
{
try
{ handleResponse(); }
catch (RetryException re)
{ this.retry = response.retry; }
}
return retry;
}
/**
* produces a full list of headers and their values, one per line.
*
* @return a string containing the headers
*/
public String toString()
{
if (!initialized)
{
try
{ handleResponse(); }
catch (Exception e)
{
if (!(e instanceof InterruptedIOException))
{
Log.write(Log.RESP, "HResp: (\"" + method + " " +
OriginalURI.getPathAndQuery() + "\")");
Log.write(Log.RESP, " ", e);
}
return "Failed to read headers: " + e;
}
}
String nl = System.getProperty("line.separator", "\n");
StringBuffer str = new StringBuffer(Version);
str.append(' ');
str.append(StatusCode);
str.append(' ');
str.append(ReasonLine);
str.append(nl);
if (EffectiveURI != null)
{
str.append("Effective-URI: ");
str.append(EffectiveURI);
str.append(nl);
}
Enumeration hdr_list = Headers.keys();
while (hdr_list.hasMoreElements())
{
String hdr = (String) hdr_list.nextElement();
str.append(hdr);
str.append(": ");
str.append(Headers.get(hdr));
str.append(nl);
}
return str.toString();
}
// Helper Methods
HTTPClientModule[] getModules()
{
return modules;
}
/**
* Processes a Response. This is done by calling the response handler
* in each module. When all is done, the various fields of this instance
* are intialized from the last Response.
*
* @exception IOException if any handler throws an IOException.
* @exception ModuleException if any module encounters an exception.
* @return true if a new request was generated. This is used for internal
* subrequests only
*/
synchronized boolean handleResponse() throws IOException, ModuleException
{
if (initialized) return false;
/* first get the response if necessary */
if (out_stream != null)
{
response = out_stream.getResponse();
response.http_resp = this;
out_stream = null;
}
/* go through modules and handle them */
doModules: while (true)
{
Phase1: for (int idx=0; idx