MonitoredResponse.java :  » Web-Server » simple » simple » http » Java Open Source

Java Open Source » Web Server » simple 
simple » simple » http » MonitoredResponse.java
/*
 * MonitoredResponse.java February 2001
 *
 * Copyright (C) 2001, Niall Gallagher <niallg@users.sf.net>
 *
 * 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.
 *
 * 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
 */
 
package simple.http;

import simple.util.parse.ContentParser;
import simple.util.net.ContentType;
import simple.util.net.Cookie;
import java.net.InetAddress;
import java.io.OutputStream;
import java.io.PrintStream;
import java.io.IOException;

/**
 * This is a <code>MonitoredResponse</code> object that is used to
 * encapsulate a HTTP response. This is a <code>Response</code>
 * object. The intended use of this object is that it be passed to 
 * a <code>ProtocolHandler</code> object and used to process a HTTP
 * transaction. 
 * <p>
 * This <code>Response</code> will send any critical events to an 
 * <code>OutputMonitor</code> for processing, this may have the 
 * effect of closing the <code>OutputStream</code> used by the 
 * <code>Response</code>.
 * <p>
 * This provides features which allow the message body to be blocked
 * if it should not be sent with the headers as of RFC 2616 rules for 
 * the presence of a message body. A message body must not be 
 * included with a HEAD request or with a 304 or a 204 response.
 *
 * @author Niall Gallagher
 */ 
final class MonitoredResponse extends ResponseHeader implements ResponseChannel {    

   /**
    * This enables buffering to be done for the response.
    */
   private ResponseStream buf;
   
   /**
    * The monitor that will be given notification of events. 
    */
   private OutputMonitor mon;
   
   /**
    * This is the <code>OutputStream</code> for the response.
    */
   private OutputStream out;

   /**
    * This contains attributes specific to this response.
    */ 
   private Attributes local;
   
   /**
    * This is the pipeline that the request is from.
    */
   private Pipeline pipe; 
   
   /**
    * This is the request instance for this transaction.
    */   
   private Request req;
   
   /**
    * Indicates the status of this HTTP response.
    */
   private boolean committed;
   
   /**
    * Contains the value of the Content-Length header.
    */
   private int length = -1;

   /**
    * This creates an implementation of the <code>Response</code>
    * interface. This implementation uses a <code>Pipeline</code>
    * for its output. Each <code>Response</code> has a matching
    * <code>Request</code> object which specifies what
    * <code>Response</code> it wants.
    *
    * @param req the <code>Request</code> associated with this
    * @param pipe this is the where the output of this is written
    * @param mon the monitor that is to be used to monitor output
    *
    * @exception IOException thrown if there is any I/O errors
    */ 
   public MonitoredResponse(Request req, Pipeline pipe, OutputMonitor mon) 
   throws IOException {
      this.out = pipe.getOutputStream();
      this.buf = new ResponseStream(this);
      this.pipe = pipe;
      this.req = req;
      this.mon = mon;      
   }

   /**
    * This can be used to determine whether the <code>Response</code>
    * has been committed. This is true if the <code>Response</code> 
    * was committed, either due to an explicit invocation of the
    * <code>commit</code> method or due to the writing of content. If
    * the <code>Response</code> has committed the <code>reset</code> 
    * method will not work in resetting content already written.
    *
    * @return true if the <code>Response</code> has been committed
    */    
   public boolean isCommitted() {
      return committed;
   }

   /**
    * The <code>reset</code> method is used to reset the output 
    * from an issued <code>OutputStream</code>. This will not work
    * is the <code>isCommitted</code> returns true. If the streams
    * byte buffer overflows the response will commit and the
    * <code>reset</code> will fail.
    */
   public void reset(){
      if(!committed){
         buf.reset();
      }
   }
   
   /**
    * This is used to write the header that where given to the
    * <code>Response</code>. Any further attempts to give headers 
    * to the <code>Response</code> will be futile as only the HTTP
    * headers that were given at the time of the first commit will 
    * be used in the message header.
    *
    * @exception IOException if there is an I/O problem committing
    */ 
   private void ensureCommit() throws IOException {
      if(!committed) {
         commit(); 
      }
   }
   
   /**
    * This is used to determine if the <code>Response</code> has a
    * message body. If this does not have a message body then false 
    * is returned. This is determined as of RFC 2616 rules for the 
    * presence of a message body. A message body must not be 
    * included with a HEAD request or with a 304 or a 204 response. 
    * If when this is called there is no message length delimiter 
    * as specified by section RFC 2616 4.4, then there is no body.
    *
    * @return if the message has a body this returns true
    */ 
   private boolean hasBody() {
      if(req.getMethod().equals("HEAD")){
         return false;
      } else if(getCode() == 204){ /* sec 4.4 */
         return false;
      } else if(getCode() == 304){ /* sec 4.4 */
         return false;
      }      
      return true;   
   }
   
   /**
    * The <code>isKeepAlive</code> method is used to determine if
    * the connection semantics are set to maintain the connection.
    * If this is true then the <code>setClose</code> method should
    * be false unless the <code>getContentLength</code> and the
    * <code>isChunked</code> reveal that there is no specified
    * connection semantics set for the response channel.
    *
    * @return true if the response connection is to be maintained
    */   
   public boolean isKeepAlive() {
      if(contains("Connection")){
         return !contains("Connection", "close");
      }
      return req.isKeepAlive();
   }
   
   /**
    * The <code>isChunked</code> is used to determine whether the
    * chunked encoding scheme is desired. This is enables data to
    * be encoded in such a way that a connection can be maintained
    * without a Content-Length header. If the output is chunked 
    * then the <code>setClose</code> should be false.
    *
    * @return true if the response output is chunked encoded
    */
   public boolean isChunked(){
      return contains("Transfer-Encoding", "chunked");
   } 
   
   /**
    * The <code>isChunkable</code> is used to determine whether the
    * chunked transfer encoding is acceptable to the client. This
    * will determine, according to RFC 2616, whether the client can
    * decode data written using the chunked encoding scheme.
    * <p>
    * RFC 2616, section 3.6 states that, A server must not send any
    * transfer-codings to an HTTP/1.0 client. RFC 2616 section 3.6.1
    * also states that an origin server should not use trailers in 
    * chunked encoded unless the trailers consist of optional meta 
    * data or the client explicitly states that it will accept them.
    *
    * @return this returns true if the client supports HTTP/1.1
    */
   public boolean isChunkable() {
      if(req.getMajor() > 1) {
         return true;         
      } else if(req.getMajor() == 1) {
         return req.getMinor() > 0;
      }
      return false;      
   }

   /**
    * This is used when the output is encoded in the chunked encoding.
    * This should only be used if <code>isChunkable</code> is true.
    * If this is used the <code>OutputStream</code> will encode data
    * using the transfer coding, specified in RFC 2616 section 3.6.1.
    * If this is set to true then <code>setClose</code> should be
    * set to false, this will ensure that connections are persistent.
    * 
    * @param chunked used to determine whether output is chunked
    */    
   public void setChunked(boolean chunked){
      if(!chunked && contains("Transfer-Encoding")){
         removeAll("Transfer-Encoding");
      }else if(chunked){
         set("Transfer-Encoding", "chunked");
      }
   } 

   /**
    * The <code>getContentLength</code> method is used to determine
    * the length that has been set with the Content-Length header
    * field. For the connection to be maintained as keep-alive the
    * number of bytes written to the <code>ResponseStream</code>
    * should be equal to the Content-Length value. If this is 
    * greater than -1 the <code>setClose</code> should be false.
    *
    * @return this returns the value in the Content-Length header
    */   
   public int getContentLength() {
      if(length >= 0) return length;
      try {
         length = parseLength();
      }catch(NumberFormatException e){                  
      }
      return length;
   }
   
   /**
    * This is an attempt to parse the Content-Length header. If the
    * Content-Length header does not exist then the length returned 
    * is -1. If the Content-Length header cannot be parsed then 
    * a <code>NumberFormatException</code> is thrown by this method.
    *
    * @return this returns the value of the Content-Length header
    *
    * @exception NumberFormatException if Content-Length is invalid
    */ 
   private int parseLength() throws NumberFormatException {
      int index = indexOf("Content-Length");
      if(index >= 0){
         String text = getValue(index);
         return Integer.parseInt(text);
      }
      return -1;     
   }

   /**
    * The allows the <code>ResponseChannel</code> to be configured
    * to have keep-alive semantics by setting the delimiter to the
    * number of bytes written in the response. This should be 
    * greater than or equal to zero. If this is set to a valid
    * length value then the <code>setClose</code> is false.
    *
    * @param length the length used in the Content-Length header
    */   
   public void setContentLength(int length) {
      set("Content-Length", String.valueOf(length));
   }

   /**
    * The <code>setClose</code> is used to set the Connection
    * header semantics. If <code>setClose</code> is false then
    * the connection semantics are keep-alive if true then the
    * semantics should be close. This is used as a fallback
    * delimiter when there has been no specific delimiter set.
    *
    * @param close specifies if the connection is keep alive
    */   
   public void setClose(boolean close) {
      set("Connection", close? "close":"keep-alive");
   }
   
   /**
    * This is used to ensure that the <code>Cookie</code> objects
    * that were set using the <code>State</code> are sent with 
    * the response. This uses the <code>State</code> issued by
    * the <code>Request.getState</code> and iteratively writes
    * the <code>getSetCookies</code> issued as Set-Cookie headers.
    *
    * @param state <code>State</code> issued by the request
    */
   private void setCookies(State state){
      Cookie[] list = state.getSetCookies();      

      for(int i = 0; i < list.length; i++){
         add("Set-Cookie", list[i].toString());
      }
   }
   
   /**
    * This is used to get a <code>ContentType</code> object for the MIME
    * type value of the Content-Type header. This is used to that the
    * MIME type of the <code>Response</code> can be examined and so
    * that the charset value of the MIME type can be determined. This
    * is useful in determining what encoding that is to be used by
    * the <code>PrintStream</code> objects created.
    *
    * @return returns the Content-Type as a <code>ContentType</code>
    */
   private ContentType getContentType(){
      int index = indexOf("Content-Type");
      if(index >= 0) {
         String text = getValue(index);
         return new ContentParser(text);   
      }
      return null;      
   }

   /**
    * This determines the charset for <code>PrintStream</code> objects
    * returned from the <code>getPrintStream</code> method. This will
    * return a valid charset regardless of whether the Content-Type
    * header has been set, set without a charset, or not set at all.
    * If unspecified, the charset returned is <code>ISO-8859-1</code>,
    * as suggested by RFC 2616, section 3.7.1.
    *
    * @return returns the charset used by this <code>Response</code>
    */
   private String getCharset() {
      ContentType type = getContentType();      
      if(type == null) {         
         return "iso-8859-1";
      }
      if(type.getCharset()==null){
         return "iso-8859-1";
      }
      return type.getCharset();
   }
   
   /**
    * This can be used to retrieve certain attributes about
    * this <code>Response</code>. The attributes contains certain
    * properties about the <code>Response</code>. For example if
    * this Response goes over a secure line then there may be any
    * arbitrary attributes.
    *
    * @return the attributes of this <code>Response</code> object
    */ 
   public Attributes getAttributes() {
      if(local == null) {
         local = new PlainAttributes(pipe);
      }
      return local;
   }
   
   /**
    * This is used as a shortcut for acquiring attributes for the
    * response. This avoids acquiring the <code>Attributes</code>
    * in order to retrieve the attribute directly from that object.
    * The attributes contain data specific to the response.
    *
    * @param name this is the name of the attribute to acquire
    *
    * @return this returns the attribute for the specified name
    */    
   public Object getAttribute(String name) {
      return getAttributes().get(name);
   }
   
   /**
    * This can be used to get the I.P address for the browser that
    * the <code>Response</code> goes to. The <code>Pipeline</code> 
    * shares this information. This method is used to that objects
    * can determine, based on the retrieved I.P address, what type
    * of output is suitable. Statistics such as location can be 
    * determined based on the DNS address obtained.
    *
    * @return returns the source I.P. address of the client
    */ 
   public InetAddress getInetAddress() {
      return pipe.getInetAddress();
   }
   
   /**
    * Used to write a message body with the <code>Response</code>. The 
    * semantics of this <code>OutputStream</code> will be determined 
    * by the HTTP version of the client, and whether or not the content
    * length has been set, through the <code>setContentLength</code>
    * method. If the length of the output is not known then the output
    * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients.
    *
    * @exception IOException this is thrown if there was an I/O error
    *
    * @return an <code>OutputStream</code> with the specified semantics
    */ 
   public OutputStream getOutputStream(){
      return buf;
   }
   
   /**
    * Used to write a message body with the <code>Response</code>. The 
    * semantics of this <code>OutputStream</code> will be determined 
    * by the HTTP version of the client, and whether or not the content
    * length has been set, through the <code>setContentLength</code>
    * method. If the length of the output is not known then the output
    * is chunked for HTTP/1.1 clients and closed for HTTP/1.0 clients.
    * <p>
    * This will ensure that there is buffering done so that the output
    * can be reset using the <code>reset</code> method. This will 
    * enable the specified number of bytes to be written without
    * committing the response. This specified size is the minimum size
    * that the response buffer must be. 
    *
    * @param size the minimum size that the response buffer must be
    *
    * @exception IOException this is thrown if there was an I/O error
    *
    * @return an <code>OutputStream</code> with the specified semantics
    */ 
   public OutputStream getOutputStream(int size){
      buf.ensureCapacity(size);
      return buf;
   }
   
   /**
    * This method is provided for convenience so that the HTTP content
    * can be written using the <code>print</code> methods provided by
    * the <code>PrintStream</code>. This will basically wrap the 
    * <code>getOutputStream</code> with a buffer size of zero.
    * <p>
    * The retrieved <code>PrintStream</code> uses the charset used to
    * describe the content, with the Content-Type header. This will
    * check the charset parameter of the contents MIME type. So if 
    * the Content-Type was <code>text/plain; charset=UTF-8</code> the
    * resulting <code>PrintStream</code> would encode the written data
    * using the UTF-8 encoding scheme. Care must be taken to ensure
    * that bytes written to the stream are correctly encoded.
    *
    * @return a <code>PrintStream</code> that provides convenience
    * methods to the <code>Response</code> for writing content
    *
    * @exception IOException this is thrown if there was an I/O error
    */
   public PrintStream getPrintStream() throws IOException {
      return getPrintStream(0, getCharset());
   }
   
   /**
    * This method is provided for convenience so that the HTTP content
    * can be written using the <code>print</code> methods provided by
    * the <code>PrintStream</code>. This will basically wrap the 
    * <code>getOutputStream</code> with a specified buffer size.
    * <p>
    * The retrieved <code>PrintStream</code> uses the charset used to
    * describe the content, with the Content-Type header. This will
    * check the charset parameter of the contents MIME type. So if 
    * the Content-Type was <code>text/plain; charset=UTF-8</code> the
    * resulting <code>PrintStream</code> would encode the written data
    * using the UTF-8 encoding scheme. Care must be taken to ensure
    * that bytes written to the stream are correctly encoded.
    *
    * @param size the minimum size that the response buffer must be
    *
    * @return a <code>PrintStream</code> that provides convenience
    * methods to the <code>Response</code> for writing content
    *
    * @exception IOException this is thrown if there was an I/O error
    */
   public PrintStream getPrintStream(int size) throws IOException {
      return getPrintStream(size, getCharset());
   }
   
   /**  
    * This is used to wrap the <code>getOutputStream</code> object in
    * a <code>PrintStream</code>, which will write content using a 
    * specified charset. The <code>PrintStream</code> created will not
    * buffer the content, it will write directly to the underlying
    * <code>OutputStream</code> where it is buffered (if there is a
    * buffer size greater than zero specified). In future the buffer
    * of the <code>PrintStream</code> may be usable.
    *
    * @param size the minimum size that the response buffer must be
    * @param charset this is the charset used by the resulting stream
    *
    * @return a <code>PrintStream</code> that provides convenience
    * methods to the <code>Response</code> for writing content
    *
    * @exception IOException the <code>IOException</code> is thrown
    * if the Content-Type charset is not supported     
    */
   private PrintStream getPrintStream(int size, String charset) throws IOException {
      return new PrintStream(getOutputStream(size), false, charset);
   }

   /**
    * This is used to write the headers that where given to the
    * <code>Response</code>. Any further attempts to give headers 
    * to the <code>Response</code> will be futile as only the headers
    * that were given at the time of the first commit will be used 
    * in the message header.
    * <p>
    * This also performs some final checks on the headers submitted.
    * This is done to determine the optimal performance of the 
    * output. If no specific Connection header has been specified
    * this will set the connection so that HTTP/1.0 closes by default.
    *
    * @exception IOException thrown if there was a problem writing
    */
   public void commit() throws IOException {
      if(!committed) {
         setCookies(req.getState());    
         String head = toString();
         try {
            out.write(head.getBytes("iso-8859-1"));
         }catch(IOException cause){
            mon.notifyError(out);   
            throw cause;
         }                  
         doConfigure();         
         committed = true;
      }
   }

   /**
    * This method is used to configure the output to the specified
    * semantics. This is used by the <code>commit</code> method to
    * ensure that the <code>getOutputChannel</code> method issues a
    * channel which can be used to communicate with the client.
    * <p>
    * It is important that the <code>OutputStream</code> perform 
    * monitoring so that persistent connections can be maintained
    * by the server. If there is no output then this will use the
    * <code>NullOutputStream</code>.
    *
    * @exception IOException thrown if there is an I/O problem
    */
   private void doConfigure() throws IOException {
      int size = getContentLength();
      boolean hasBody = hasBody();
      
      if(hasBody && !isKeepAlive()) {
         out = new CloseOutputStream(out,mon);
      }else if(hasBody && isChunked()){
         out = new ChunkedOutputStream(out,mon);
      }else if(hasBody && size > 0){
         out = new FixedOutputStream(out,mon,size);
      } else {
         out = new NullOutputStream(out,mon);
      }
   }
   
   /** 
    * The retrieves an <code>OutputStream</code> that represents the
    * response output. The stream issued will have the connection 
    * semantics specified. Once this method is used the connection
    * semantics are set permanently. If there is an error committing
    * the response header this throws an <code>IOException</code>.
    * 
    * @return this returns the output channel for the response body
    *
    * @exception IOException thrown if there is an I/O problem
    */ 
   public OutputStream getOutputChannel() throws IOException {
      ensureCommit();
      return out;      
   }
   
   /**
    * This will ensure that the <code>MonitoredResponse</code> object
    * will notify the <code>OutputMonitor</code> of its status. If the
    * output is not configured then the monitor will be notified of a
    * <code>notifyError</code> event, when the GC invokes this.
    * <p>
    * This is needed so that the <code>OutputMonitor</code> is used 
    * to provide a mechanism whereby separate threads can interact 
    * with the <code>MonitoredOutputStream</code> in such a way that 
    * the <code>Dispatcher.run</code> can finish and the stream can 
    * still control the connection using the <code>OutputMonitor</code>.
    * The connection will be severed if there are no more threads with
    * a reference to the <code>Response</code> object.
    *
    * @exception Throwable an <code>IOException</code> on close
    */ 
   protected void finalize() throws Throwable {
      if(!committed){
         mon.notifyError(out); 
      }else {
         buf.close();
      }
   }
   
}
java2s.com  | Contact Us | Privacy Policy
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.