| // ======================================================================== |
| // $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 org.openqa.jetty.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.apache.commons.logging.Log; |
| import org.openqa.jetty.log.LogFactory; |
| import org.openqa.jetty.util.LogSupport; |
| import org.openqa.jetty.util.StringUtil; |
| import org.openqa.jetty.util.TypeUtil; |
| import org.openqa.jetty.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.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 = org.openqa.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(); |
| } |
| |
| } |
| |
| |
| |