/* * Copyright 2002-2004 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code 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 General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ import java.io.*; import java.nio.*; import java.nio.channels.*; import java.net.*; import sun.net.www.MessageHeader; /** * This class encapsulates a HTTP request received and a response to be * generated in one transaction. It provides methods for examaining the * request from the client, and for building and sending a reply. */ public class HttpTransaction { String command; URI requesturi; HttpServer.ServerWorker server; MessageHeader reqheaders, reqtrailers; String reqbody; byte[] rspbody; MessageHeader rspheaders, rsptrailers; SocketChannel ch; int rspbodylen; boolean rspchunked; HttpTransaction (HttpServer.ServerWorker server, String command, URI requesturi, MessageHeader headers, String body, MessageHeader trailers, SocketChannel ch) { this.command = command; this.requesturi = requesturi; this.reqheaders = headers; this.reqbody = body; this.reqtrailers = trailers; this.ch = ch; this.server = server; } /** * Get the value of a request header whose name is specified by the * String argument. * * @param key the name of the request header * @return the value of the header or null if it does not exist */ public String getRequestHeader (String key) { return reqheaders.findValue (key); } /** * Get the value of a response header whose name is specified by the * String argument. * * @param key the name of the response header * @return the value of the header or null if it does not exist */ public String getResponseHeader (String key) { return rspheaders.findValue (key); } /** * Get the request URI * * @return the request URI */ public URI getRequestURI () { return requesturi; } public String toString () { StringBuffer buf = new StringBuffer(); buf.append ("Request from: ").append (ch.toString()).append("\r\n"); buf.append ("Command: ").append (command).append("\r\n"); buf.append ("Request URI: ").append (requesturi).append("\r\n"); buf.append ("Headers: ").append("\r\n"); buf.append (reqheaders.toString()).append("\r\n"); buf.append ("Body: ").append (reqbody).append("\r\n"); buf.append ("---------Response-------\r\n"); buf.append ("Headers: ").append("\r\n"); if (rspheaders != null) { buf.append (rspheaders.toString()).append("\r\n"); } buf.append ("Body: ").append (new String(rspbody)).append("\r\n"); return new String (buf); } /** * Get the value of a request trailer whose name is specified by * the String argument. * * @param key the name of the request trailer * @return the value of the trailer or null if it does not exist */ public String getRequestTrailer (String key) { return reqtrailers.findValue (key); } /** * Add a response header to the response. Multiple calls with the same * key value result in multiple header lines with the same key identifier * @param key the name of the request header to add * @param val the value of the header */ public void addResponseHeader (String key, String val) { if (rspheaders == null) rspheaders = new MessageHeader (); rspheaders.add (key, val); } /** * Set a response header. Searches for first header with named key * and replaces its value with val * @param key the name of the request header to add * @param val the value of the header */ public void setResponseHeader (String key, String val) { if (rspheaders == null) rspheaders = new MessageHeader (); rspheaders.set (key, val); } /** * Add a response trailer to the response. Multiple calls with the same * key value result in multiple trailer lines with the same key identifier * @param key the name of the request trailer to add * @param val the value of the trailer */ public void addResponseTrailer (String key, String val) { if (rsptrailers == null) rsptrailers = new MessageHeader (); rsptrailers.add (key, val); } /** * Get the request method * * @return the request method */ public String getRequestMethod (){ return command; } /** * Perform an orderly close of the TCP connection associated with this * request. This method guarantees that any response already sent will * not be reset (by this end). The implementation does a shutdownOutput() * of the TCP connection and for a period of time consumes and discards * data received on the reading side of the connection. This happens * in the background. After the period has expired the * connection is completely closed. */ public void orderlyClose () { try { server.orderlyCloseChannel (ch); } catch (IOException e) { System.out.println (e); } } /** * Do an immediate abortive close of the TCP connection associated * with this request. */ public void abortiveClose () { try { server.abortiveCloseChannel(ch); } catch (IOException e) { System.out.println (e); } } /** * Get the SocketChannel associated with this request * * @return the socket channel */ public SocketChannel channel() { return ch; } /** * Get the request entity body associated with this request * as a single String. * * @return the entity body in one String */ public String getRequestEntityBody (){ return reqbody; } /** * Set the entity response body with the given string * The content length is set to the length of the string * @param body the string to send in the response */ public void setResponseEntityBody (String body){ rspbody = body.getBytes(); rspbodylen = body.length(); rspchunked = false; addResponseHeader ("Content-length", Integer.toString (rspbodylen)); } /** * Set the entity response body with the given byte[] * The content length is set to the gven length * @param body the string to send in the response */ public void setResponseEntityBody (byte[] body, int len){ rspbody = body; rspbodylen = len; rspchunked = false; addResponseHeader ("Content-length", Integer.toString (rspbodylen)); } /** * Set the entity response body by reading the given inputstream * * @param is the inputstream from which to read the body */ public void setResponseEntityBody (InputStream is) throws IOException { byte[] buf = new byte [2048]; byte[] total = new byte [2048]; int total_len = 2048; int c, len=0; while ((c=is.read (buf)) != -1) { if (len+c > total_len) { byte[] total1 = new byte [total_len * 2]; System.arraycopy (total, 0, total1, 0, len); total = total1; total_len = total_len * 2; } System.arraycopy (buf, 0, total, len, c); len += c; } setResponseEntityBody (total, len); } /* chunked */ /** * Set the entity response body with the given array of strings * The content encoding is set to "chunked" and each array element * is sent as one chunk. * @param body the array of string chunks to send in the response */ public void setResponseEntityBody (String[] body) { StringBuffer buf = new StringBuffer (); int len = 0; for (int i=0; i<body.length; i++) { String chunklen = Integer.toHexString (body[i].length()); len += body[i].length(); buf.append (chunklen).append ("\r\n"); buf.append (body[i]).append ("\r\n"); } buf.append ("0\r\n"); rspbody = new String (buf).getBytes(); rspbodylen = rspbody.length; rspchunked = true; addResponseHeader ("Transfer-encoding", "chunked"); } /** * Send the response with the current set of response parameters * but using the response code and string tag line as specified * @param rCode the response code to send * @param rTag the response string to send with the response code */ public void sendResponse (int rCode, String rTag) throws IOException { OutputStream os = new HttpServer.NioOutputStream(channel(), server.getSSLEngine(), server.outNetBB(), server.outAppBB()); PrintStream ps = new PrintStream (os); ps.print ("HTTP/1.1 " + rCode + " " + rTag + "\r\n"); if (rspheaders != null) { rspheaders.print (ps); } else { ps.print ("\r\n"); } ps.flush (); if (rspbody != null) { os.write (rspbody, 0, rspbodylen); os.flush(); } if (rsptrailers != null) { rsptrailers.print (ps); } else if (rspchunked) { ps.print ("\r\n"); } ps.flush(); } /* sends one byte less than intended */ public void sendPartialResponse (int rCode, String rTag)throws IOException { OutputStream os = new HttpServer.NioOutputStream(channel(), server.getSSLEngine(), server.outNetBB(), server.outAppBB()); PrintStream ps = new PrintStream (os); ps.print ("HTTP/1.1 " + rCode + " " + rTag + "\r\n"); ps.flush(); if (rspbody != null) { os.write (rspbody, 0, rspbodylen-1); os.flush(); } if (rsptrailers != null) { rsptrailers.print (ps); } ps.flush(); } }