// ======================================================================== // 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\nError "); writer.write(Integer.toString(code)); writer.write(' '); writer.write(message); writer.write("\n\n

HTTP ERROR: "); writer.write(Integer.toString(code)); writer.write(' '); writer.write(message); writer.write("

\n"); writer.write("RequestURI="); writer.write(uri); for (int i=0;i<20;i++) writer.write("\n "); writer.write("\n\n\n"); } /* ------------------------------------------------------------- */ /** * Sends an error response to the client using the specified status * code and no default message. * @param code the status code * @exception IOException If an I/O error has occurred. */ public void sendError(int code) throws IOException { sendError(code,null); } /* ------------------------------------------------------------- */ /** * Sends a redirect response to the client using the specified redirect * location URL. * @param location the redirect location URL * @exception IOException If an I/O error has occurred. */ public void sendRedirect(String location) throws IOException { if (isCommitted()) throw new IllegalStateException("Commited"); _header.put(HttpFields.__Location,location); setStatus(__302_Moved_Temporarily); commit(); } /* -------------------------------------------------------------- */ /** Add a Set-Cookie field. */ public void addSetCookie(String name, String value) { addSetCookie(new Cookie(name,value),false); } /* -------------------------------------------------------------- */ /** Add a Set-Cookie field. */ public void addSetCookie(Cookie cookie) { addSetCookie(cookie,false); } /* -------------------------------------------------------------- */ /** Add a Set-Cookie field. * @param cookie The cookie. * @param cookie2 If true, use the alternate cookie 2 header */ public void addSetCookie(Cookie cookie, boolean cookie2) { _header.addSetCookie(cookie,cookie2); } /* ------------------------------------------------------------ */ /** * @exception IOException */ public void commit() throws IOException { if (!isCommitted()) getOutputStream().flush(); } /* ------------------------------------------------------------ */ /** Recycle the response. */ void recycle(HttpConnection connection) { super.recycle(connection); _status=__200_OK; _reason=null; _httpContext=null; } /* ------------------------------------------------------------ */ /** Destroy the response. * Help the garbage collector by null everything that we can. */ public void destroy() { _reason=null; super.destroy(); } }