// ========================================================================
// Copyright (c) 1999 Mort Bay Consulting (Australia) Pty. Ltd.
// $Id: HttpResponse.java,v 1.15.2.10 2003/06/04 04:47:42 starksm Exp $
// ========================================================================
package org.mortbay.http;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import javax.servlet.http.Cookie;
import org.mortbay.util.ByteArrayISO8859Writer;
import org.mortbay.util.Code;
import org.mortbay.util.StringUtil;
import org.mortbay.util.TypeUtil;
import org.mortbay.util.URI;
import org.mortbay.util.UrlEncoded;
/* ------------------------------------------------------------ */
/** HTTP Response.
* This class manages the headers, trailers and content streams
* of a HTTP response. It can be used for receiving or generating
* requests.
*
* This class is not synchronized. It should be explicitly
* synchronized if it is used by multiple threads.
*
* @see HttpRequest
* @version $Id: HttpResponse.java,v 1.15.2.10 2003/06/04 04:47:42 starksm Exp $
* @author Greg Wilkins (gregw)
*/
public class HttpResponse extends HttpMessage
{
public final static int
__100_Continue = 100,
__101_Switching_Protocols = 101,
__102_Processing = 102,
__200_OK = 200,
__201_Created = 201,
__202_Accepted = 202,
__203_Non_Authoritative_Information = 203,
__204_No_Content = 204,
__205_Reset_Content = 205,
__206_Partial_Content = 206,
__207_Multi_Status = 207,
__300_Multiple_Choices = 300,
__301_Moved_Permanently = 301,
__302_Moved_Temporarily = 302,
__303_See_Other = 303,
__304_Not_Modified = 304,
__305_Use_Proxy = 305,
__400_Bad_Request = 400,
__401_Unauthorized = 401,
__402_Payment_Required = 402,
__403_Forbidden = 403,
__404_Not_Found = 404,
__405_Method_Not_Allowed = 405,
__406_Not_Acceptable = 406,
__407_Proxy_Authentication_Required = 407,
__408_Request_Timeout = 408,
__409_Conflict = 409,
__410_Gone = 410,
__411_Length_Required = 411,
__412_Precondition_Failed = 412,
__413_Request_Entity_Too_Large = 413,
__414_Request_URI_Too_Large = 414,
__415_Unsupported_Media_Type = 415,
__416_Requested_Range_Not_Satisfiable = 416,
__417_Expectation_Failed = 417,
__422_Unprocessable_Entity = 422,
__423_Locked = 423,
__424_Failed_Dependency = 424,
__500_Internal_Server_Error = 500,
__501_Not_Implemented = 501,
__502_Bad_Gateway = 502,
__503_Service_Unavailable = 503,
__504_Gateway_Timeout = 504,
__505_HTTP_Version_Not_Supported = 505,
__507_Insufficient_Storage = 507;
/* -------------------------------------------------------------- */
public final static HashMap __statusMsg = new HashMap();
static
{
// Build error code map using reflection
try
{
Field[] fields = org.mortbay.http.HttpResponse.class
.getDeclaredFields();
for (int f=fields.length; f-->0 ;)
{
int m = fields[f].getModifiers();
String name=fields[f].getName();
if (Modifier.isFinal(m) &&
Modifier.isStatic(m) &&
fields[f].getType().equals(Integer.TYPE) &&
name.startsWith("__") &&
Character.isDigit(name.charAt(2)))
{
String message = name.substring(6);
message = message.replace('_',' ');
__statusMsg.put(fields[f].get(null),message);
}
}
}
catch (Exception e)
{
Code.warning(e);
}
}
/* ------------------------------------------------------------ */
static byte[] __Continue;
static
{
try{
__Continue="HTTP/1.1 100 Continue\015\012\015\012".getBytes(StringUtil.__ISO_8859_1);
}
catch (Exception e){Code.fail(e);}
}
/* -------------------------------------------------------------- */
private int _status= __200_OK;
private String _reason;
private HttpContext _httpContext;
/* ------------------------------------------------------------ */
/** Constructor.
*/
public HttpResponse()
{
_version=__HTTP_1_0;
_state=__MSG_EDITABLE;
}
/* ------------------------------------------------------------ */
/** Constructor.
* @param connection
*/
public HttpResponse(HttpConnection connection)
{
super(connection);
_version=__HTTP_1_0;
_state=__MSG_EDITABLE;
}
/* ------------------------------------------------------------ */
/** Get the HttpContext handling this reponse.
* @return The HttpContext that is handling this request.
*/
public HttpContext getHttpContext()
{
return _httpContext;
}
/* ------------------------------------------------------------ */
/** Set the HttpContext handling this reponse.
* @param context The HttpContext handling this reponse.
*/
void setHttpContext(HttpContext context)
{
_httpContext=context;
}
/* ------------------------------------------------------------ */
/**
* @return true if the message has been modified.
*/
public boolean isDirty()
{
return _status!=__200_OK || super.isDirty();
}
/* ------------------------------------------------------------ */
/** Reset the response.
* Clears any data that exists in the buffer as well as the status
* code. If the response has been committed, this method throws an
* IllegalStateException.
*
* @exception IllegalStateException if the response has already been
* committed
*/
public void reset()
{
if (isCommitted())
throw new IllegalStateException("Already committed");
try
{
((HttpOutputStream)getOutputStream()).resetBuffer();
_status= __200_OK;
_reason=null;
super.reset();
}
catch(Exception e)
{
Code.warning(e);
throw new IllegalStateException(e.toString());
}
}
/* ------------------------------------------------------------ */
/**
* @deprecated use getHttpRequest()
*/
public HttpRequest getRequest()
{
return getHttpRequest();
}
/* ------------------------------------------------------------ */
/** Get the HTTP Request.
* Get the HTTP Request associated with this response.
* @return associated request
*/
public HttpRequest getHttpRequest()
{
if (_connection==null)
return null;
return _connection.getRequest();
}
/* ------------------------------------------------------------ */
/** Not Implemented.
* @param in
* @exception IOException
*/
public void readHeader(HttpInputStream in)
throws IOException
{
_state=__MSG_BAD;
Code.notImplemented();
}
/* -------------------------------------------------------------- */
public void writeHeader(Writer writer)
throws IOException
{
if (_state!=__MSG_EDITABLE)
throw new IllegalStateException(__state[_state]+
" is not EDITABLE");
if (_header==null)
throw new IllegalStateException("Response is destroyed");
if (getHttpRequest().getDotVersion()>=0)
{
_state=__MSG_BAD;
writer.write(_version);
writer.write(' ');
writer.write('0'+((_status/100)%10));
writer.write('0'+((_status/10)%10));
writer.write('0'+(_status%10));
writer.write(' ');
writer.write(getReason());
writer.write(HttpFields.__CRLF);
_header.write(writer);
}
_state=__MSG_SENDING;
}
/* -------------------------------------------------------------- */
public int getStatus()
{
return _status;
}
/* -------------------------------------------------------------- */
public void setStatus(int status)
{
_status=status;
}
/* -------------------------------------------------------------- */
public String getReason()
{
if (_reason!=null)
return _reason;
_reason=(String)__statusMsg.get(TypeUtil.newInteger(_status));
if (_reason==null)
_reason="unknown";
return _reason;
}
/* -------------------------------------------------------------- */
public void setReason(String reason)
{
_reason=reason;
}
/* ------------------------------------------------------------ */
/* Which fields to set?
* Specialized HttpMessage.setFields to consult request TE field
* for a "trailer" token if state is SENDING.
* @return Header or Trailer fields
* @exception IllegalStateException Not editable or sending 1.1
* with trailers
*/
protected HttpFields setFields()
throws IllegalStateException
{
if (!_acceptTrailer &&
_state==__MSG_SENDING &&
_version.equals(__HTTP_1_1))
{
HttpRequest request=_connection.getRequest();
if (request!=null)
request.getAcceptableTransferCodings();
}
return super.setFields();
}
/* ------------------------------------------------------------- */
/** Send Error Response.
*/
public void sendError(int code,String message)
throws IOException
{
Integer code_integer=TypeUtil.newInteger(code);
if (message == null)
message = (String)__statusMsg.get(code_integer);
HttpRequest request=getHttpRequest();
Class exClass=(Class)request.getAttribute("javax.servlet.error.exception_type");
// Generate normal error page.
setStatus(code);
setReason(UrlEncoded.encodeString(message));
// If we are allowed to have a body
if (code!=__204_No_Content &&
code!=__304_Not_Modified &&
code!=__206_Partial_Content &&
code>=200)
{
// Find error page.
String error_page = null;
while (error_page==null && exClass!=null && _httpContext!=null)
{
error_page = _httpContext.getErrorPage(exClass.getName());
exClass=exClass.getSuperclass();
Code.debug("error page: ",exClass," -> ",error_page);
}
if (error_page==null && _httpContext!=null)
{
error_page = _httpContext.getErrorPage(TypeUtil.toString(code));
Code.debug("error page: "+code," -> ",error_page);
}
// Handle error page
if (error_page!=null)
{
if (!error_page.startsWith("/"))
error_page="/"+error_page;
if (request.getAttribute("javax.servlet.error.status_code")==null)
{
// Clear old wrappers
request.setWrapper(null);
setWrapper(null);
// Set attributes to describe error
request.setAttribute("javax.servlet.error.request_uri",
getHttpRequest().getEncodedPath());
request.setAttribute("javax.servlet.error.status_code",code_integer);
request.setAttribute("javax.servlet.error.message",message);
// Change URI and the method to GET
request.setState(HttpMessage.__MSG_EDITABLE);
request.setMethod(HttpRequest.__GET);
request.getURI().setPath
(URI.addPaths(_httpContext.getContextPath(),error_page));
request.setState(HttpMessage.__MSG_RECEIVED);
// Do a forward to the error page resource.
setContentType(HttpFields.__TextHtml);
String query=null;
int q=error_page.indexOf('?');
if (q>0)
{
query=error_page.substring(q+1);
error_page=error_page.substring(0,q);
}
getHttpContext().handle(error_page,query,request,this);
}
else
Code.warning("Error "+code+" while serving error page for "+
request.getAttribute("javax.servlet.error.status_code"));
}
else
{
setContentType(HttpFields.__TextHtml);
_mimeType=HttpFields.__TextHtml;
_characterEncoding=null;
ByteArrayISO8859Writer writer = new ByteArrayISO8859Writer(1500);
writeErrorPage(writer,code,message);
writer.flush();
setContentLength(writer.size());
writer.writeTo(getOutputStream());
}
}
else if (code!=__206_Partial_Content)
{
_header.remove(HttpFields.__ContentType);
_header.remove(HttpFields.__ContentLength);
_characterEncoding=null;
_mimeType=null;
}
commit();
}
/* ------------------------------------------------------------ */
public void writeErrorPage(Writer writer, int code,String message)
throws IOException
{
if (message!=null)
{
message=StringUtil.replace(message,"<","<");
message=StringUtil.replace(message,">",">");
}
String uri=getHttpRequest().getPath();
uri=StringUtil.replace(uri,"<","<");
uri=StringUtil.replace(uri,">",">");
writer.write("\n