// ======================================================================== // $Id: HttpResponse.java,v 1.61 2005/10/26 08:10:14 gregwilkins Exp $ // Copyright 199-2004 Mort Bay Consulting Pty. Ltd. // ------------------------------------------------------------------------ // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // http://www.apache.org/licenses/LICENSE-2.0 // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // ======================================================================== package net.lightbody.bmp.proxy.jetty.http; import net.lightbody.bmp.proxy.jetty.log.LogFactory; import net.lightbody.bmp.proxy.jetty.util.LogSupport; import net.lightbody.bmp.proxy.jetty.util.StringUtil; import net.lightbody.bmp.proxy.jetty.util.TypeUtil; import net.lightbody.bmp.proxy.jetty.util.UrlEncoded; import org.apache.commons.logging.Log; import javax.servlet.http.Cookie; import java.io.IOException; import java.io.Writer; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.util.HashMap; /* ------------------------------------------------------------ */ /** 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.61 2005/10/26 08:10:14 gregwilkins Exp $ * @author Greg Wilkins (gregw) */ public class HttpResponse extends HttpMessage { private static Log log = LogFactory.getLog(HttpResponse.class); 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, __302_Found = 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 = net.lightbody.bmp.proxy.jetty.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) { log.warn(LogSupport.EXCEPTION,e); } } /* ------------------------------------------------------------ */ static byte[] __Continue; static { try{ __Continue="HTTP/1.1 100 Continue\015\012\015\012".getBytes(StringUtil.__ISO_8859_1); } catch (Exception e){log.fatal(e); System.exit(1);} } /* -------------------------------------------------------------- */ private int _status= __200_OK; private String _reason; private HttpContext _httpContext; /* ------------------------------------------------------------ */ /** Constructor. */ public HttpResponse() { _version=__HTTP_1_1; _dotVersion=1; _state=__MSG_EDITABLE; } /* ------------------------------------------------------------ */ /** Constructor. * @param connection */ public HttpResponse(HttpConnection connection) { super(connection); _version=__HTTP_1_1; _dotVersion=1; _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. */ public 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 * <code>IllegalStateException</code>. * * @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(); setField(HttpFields.__Date,getRequest().getTimeStampStr()); if (!Version.isParanoid()) setField(HttpFields.__Server,Version.getDetail()); } catch(Exception e) { log.warn(LogSupport.EXCEPTION,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; log.warn(LogSupport.NOT_IMPLEMENTED); } /* -------------------------------------------------------------- */ 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; } /* -------------------------------------------------------------- */ public void setStatus(int code,String message) { setStatus(code); Integer code_integer=TypeUtil.newInteger(code); if (message == null) { message = (String)__statusMsg.get(code_integer); if (message==null) message=""+code; setReason(message); } else setReason(UrlEncoded.encodeString(message)); } /* ------------------------------------------------------------- */ /** Send Error Response. */ public void sendError(int code,String message) throws IOException { setStatus(code,message); // Generate normal error page. HttpRequest request=getHttpRequest(); // If we are allowed to have a body if (code!=__204_No_Content && code!=__304_Not_Modified && code!=__206_Partial_Content && code>=200) { if (getHttpContext()!=null) { Object o= getHttpContext().getAttribute(HttpContext.__ErrorHandler); if (o!=null && o instanceof HttpHandler) ((HttpHandler)o).handle(request.getPath(), null, request, this); } } else if (code!=__206_Partial_Content) { _header.remove(HttpFields.__ContentType); _header.remove(HttpFields.__ContentLength); _characterEncoding=null; _mimeType=null; } commit(); } /* ------------------------------------------------------------- */ /** * 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) { _header.addSetCookie(new Cookie(name,value)); } /* -------------------------------------------------------------- */ /** Add a Set-Cookie field. */ public void addSetCookie(Cookie cookie) { _header.addSetCookie(cookie); } /* ------------------------------------------------------------ */ public void completing() { getHttpConnection().completing(); } /* ------------------------------------------------------------ */ /** * @exception IOException */ public void commit() throws IOException { if (!isCommitted()) getOutputStream().flush(); getHttpConnection().commit(); } /* ------------------------------------------------------------ */ /** 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(); } }