HttpTransaction.java revision 6073:cea72c2bf071
1/*
2 * Copyright (c) 2002, 2012, 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
24import java.io.*;
25import java.nio.*;
26import java.nio.channels.*;
27import java.net.*;
28import 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
36public class HttpTransaction {
37
38    String command;
39    URI requesturi;
40    TestHttpServer.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 (TestHttpServer.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 TestHttpServer.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 TestHttpServer.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}
331