1 /*
   2  * Copyright (c) 2002, 2004, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 import java.io.*;
  25 import java.nio.*;
  26 import java.nio.channels.*;
  27 import java.net.*;
  28 import sun.net.www.MessageHeader;
  29 
  30 /**
  31  * This class encapsulates a HTTP request received and a response to be
  32  * generated in one transaction. It provides methods for examaining the
  33  * request from the client, and for building and sending a reply.
  34  */
  35 
  36 public class HttpTransaction {
  37 
  38     String command;
  39     URI requesturi;
  40     HttpServer.Server server;
  41     MessageHeader reqheaders, reqtrailers;
  42     String reqbody;
  43     byte[] rspbody;
  44     MessageHeader rspheaders, rsptrailers;
  45     SelectionKey  key;
  46     int rspbodylen;
  47     boolean rspchunked;
  48 
  49     HttpTransaction (HttpServer.Server server, String command,
  50                         URI requesturi, MessageHeader headers,
  51                         String body, MessageHeader trailers, SelectionKey  key) {
  52         this.command = command;
  53         this.requesturi = requesturi;
  54         this.reqheaders = headers;
  55         this.reqbody = body;
  56         this.reqtrailers = trailers;
  57         this.key = key;
  58         this.server = server;
  59     }
  60 
  61     /**
  62      * Get the value of a request header whose name is specified by the
  63      * String argument.
  64      *
  65      * @param key the name of the request header
  66      * @return the value of the header or null if it does not exist
  67      */
  68     public String getRequestHeader (String key) {
  69         return reqheaders.findValue (key);
  70     }
  71 
  72     /**
  73      * Get the value of a response header whose name is specified by the
  74      * String argument.
  75      *
  76      * @param key the name of the response header
  77      * @return the value of the header or null if it does not exist
  78      */
  79     public String getResponseHeader (String key) {
  80         return rspheaders.findValue (key);
  81     }
  82 
  83     /**
  84      * Get the request URI
  85      *
  86      * @return the request URI
  87      */
  88     public URI getRequestURI () {
  89         return requesturi;
  90     }
  91 
  92     public String toString () {
  93         StringBuffer buf = new StringBuffer();
  94         buf.append ("Request from: ").append (key.channel().toString()).append("\r\n");
  95         buf.append ("Command: ").append (command).append("\r\n");
  96         buf.append ("Request URI: ").append (requesturi).append("\r\n");
  97         buf.append ("Headers: ").append("\r\n");
  98         buf.append (reqheaders.toString()).append("\r\n");
  99         buf.append ("Body: ").append (reqbody).append("\r\n");
 100         buf.append ("---------Response-------\r\n");
 101         buf.append ("Headers: ").append("\r\n");
 102         if (rspheaders != null) {
 103             buf.append (rspheaders.toString()).append("\r\n");
 104         }
 105         String rbody = rspbody == null? "": new String (rspbody);
 106         buf.append ("Body: ").append (rbody).append("\r\n");
 107         return new String (buf);
 108     }
 109 
 110     /**
 111      * Get the value of a request trailer whose name is specified by
 112      * the String argument.
 113      *
 114      * @param key the name of the request trailer
 115      * @return the value of the trailer or null if it does not exist
 116      */
 117     public String getRequestTrailer (String key) {
 118         return reqtrailers.findValue (key);
 119     }
 120 
 121     /**
 122      * Add a response header to the response. Multiple calls with the same
 123      * key value result in multiple header lines with the same key identifier
 124      * @param key the name of the request header to add
 125      * @param val the value of the header
 126      */
 127     public void addResponseHeader (String key, String val) {
 128         if (rspheaders == null)
 129             rspheaders = new MessageHeader ();
 130         rspheaders.add (key, val);
 131     }
 132 
 133     /**
 134      * Set a response header. Searches for first header with named key
 135      * and replaces its value with val
 136      * @param key the name of the request header to add
 137      * @param val the value of the header
 138      */
 139     public void setResponseHeader (String key, String val) {
 140         if (rspheaders == null)
 141             rspheaders = new MessageHeader ();
 142         rspheaders.set (key, val);
 143     }
 144 
 145     /**
 146      * Add a response trailer to the response. Multiple calls with the same
 147      * key value result in multiple trailer lines with the same key identifier
 148      * @param key the name of the request trailer to add
 149      * @param val the value of the trailer
 150      */
 151     public void addResponseTrailer (String key, String val) {
 152         if (rsptrailers == null)
 153             rsptrailers = new MessageHeader ();
 154         rsptrailers.add (key, val);
 155     }
 156 
 157     /**
 158      * Get the request method
 159      *
 160      * @return the request method
 161      */
 162     public String getRequestMethod (){
 163         return command;
 164     }
 165 
 166     /**
 167      * Perform an orderly close of the TCP connection associated with this
 168      * request. This method guarantees that any response already sent will
 169      * not be reset (by this end). The implementation does a shutdownOutput()
 170      * of the TCP connection and for a period of time consumes and discards
 171      * data received on the reading side of the connection. This happens
 172      * in the background. After the period has expired the
 173      * connection is completely closed.
 174      */
 175 
 176     public void orderlyClose () {
 177         try {
 178             server.orderlyCloseChannel (key);
 179         } catch (IOException e) {
 180             System.out.println (e);
 181         }
 182     }
 183 
 184     /**
 185      * Do an immediate abortive close of the TCP connection associated
 186      * with this request.
 187      */
 188     public void abortiveClose () {
 189         try {
 190             server.abortiveCloseChannel(key);
 191         } catch (IOException e) {
 192             System.out.println (e);
 193         }
 194     }
 195 
 196     /**
 197      * Get the SocketChannel associated with this request
 198      *
 199      * @return the socket channel
 200      */
 201     public SocketChannel channel() {
 202         return (SocketChannel) key.channel();
 203     }
 204 
 205     /**
 206      * Get the request entity body associated with this request
 207      * as a single String.
 208      *
 209      * @return the entity body in one String
 210      */
 211     public String getRequestEntityBody (){
 212         return reqbody;
 213     }
 214 
 215     /**
 216      * Set the entity response body with the given string
 217      * The content length is set to the length of the string
 218      * @param body the string to send in the response
 219      */
 220     public void setResponseEntityBody (String body){
 221         rspbody = body.getBytes();
 222         rspbodylen = body.length();
 223         rspchunked = false;
 224         addResponseHeader ("Content-length", Integer.toString (rspbodylen));
 225     }
 226     /**
 227      * Set the entity response body with the given byte[]
 228      * The content length is set to the gven length
 229      * @param body the string to send in the response
 230      */
 231     public void setResponseEntityBody (byte[] body, int len){
 232         rspbody = body;
 233         rspbodylen = len;
 234         rspchunked = false;
 235         addResponseHeader ("Content-length", Integer.toString (rspbodylen));
 236     }
 237 
 238 
 239     /**
 240      * Set the entity response body by reading the given inputstream
 241      *
 242      * @param is the inputstream from which to read the body
 243      */
 244     public void setResponseEntityBody (InputStream is) throws IOException {
 245         byte[] buf = new byte [2048];
 246         byte[] total = new byte [2048];
 247         int total_len = 2048;
 248         int c, len=0;
 249         while ((c=is.read (buf)) != -1) {
 250             if (len+c > total_len) {
 251                 byte[] total1 = new byte [total_len * 2];
 252                 System.arraycopy (total, 0, total1, 0, len);
 253                 total = total1;
 254                 total_len = total_len * 2;
 255             }
 256             System.arraycopy (buf, 0, total, len, c);
 257             len += c;
 258         }
 259         setResponseEntityBody (total, len);
 260     }
 261 
 262     /* chunked */
 263 
 264     /**
 265      * Set the entity response body with the given array of strings
 266      * The content encoding is set to "chunked" and each array element
 267      * is sent as one chunk.
 268      * @param body the array of string chunks to send in the response
 269      */
 270     public void setResponseEntityBody (String[] body) {
 271         StringBuffer buf = new StringBuffer ();
 272         int len = 0;
 273         for (int i=0; i<body.length; i++) {
 274             String chunklen = Integer.toHexString (body[i].length());
 275             len += body[i].length();
 276             buf.append (chunklen).append ("\r\n");
 277             buf.append (body[i]).append ("\r\n");
 278         }
 279         buf.append ("0\r\n");
 280         rspbody = new String (buf).getBytes();
 281         rspbodylen = rspbody.length;
 282         rspchunked = true;
 283         addResponseHeader ("Transfer-encoding", "chunked");
 284     }
 285 
 286     /**
 287      * Send the response with the current set of response parameters
 288      * but using the response code and string tag line as specified
 289      * @param rCode the response code to send
 290      * @param rTag the response string to send with the response code
 291      */
 292     public void sendResponse (int rCode, String rTag) throws IOException {
 293         OutputStream os = new HttpServer.NioOutputStream(channel());
 294         PrintStream ps = new PrintStream (os);
 295         ps.print ("HTTP/1.1 " + rCode + " " + rTag + "\r\n");
 296         if (rspheaders != null) {
 297             rspheaders.print (ps);
 298         } else {
 299             ps.print ("\r\n");
 300         }
 301         ps.flush ();
 302         if (rspbody != null) {
 303             os.write (rspbody, 0, rspbodylen);
 304             os.flush();
 305         }
 306         if (rsptrailers != null) {
 307             rsptrailers.print (ps);
 308         } else if (rspchunked) {
 309             ps.print ("\r\n");
 310         }
 311         ps.flush();
 312     }
 313 
 314     /* sends one byte less than intended */
 315 
 316     public void sendPartialResponse (int rCode, String rTag)throws IOException {
 317         OutputStream os = new HttpServer.NioOutputStream(channel());
 318         PrintStream ps = new PrintStream (os);
 319         ps.print ("HTTP/1.1 " + rCode + " " + rTag + "\r\n");
 320         ps.flush();
 321         if (rspbody != null) {
 322             os.write (rspbody, 0, rspbodylen-1);
 323             os.flush();
 324         }
 325         if (rsptrailers != null) {
 326             rsptrailers.print (ps);
 327         }
 328         ps.flush();
 329     }
 330 }