001: /**
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */package org.apache.openejb.server.httpd;
017:
018: import java.io.ByteArrayOutputStream;
019: import java.io.DataOutput;
020: import java.io.DataOutputStream;
021: import java.io.IOException;
022: import java.io.InputStream;
023: import java.io.OutputStream;
024: import java.io.PrintWriter;
025: import java.net.URL;
026: import java.net.URLConnection;
027: import java.util.HashMap;
028: import java.util.Map;
029: import java.util.Properties;
030: import java.util.StringTokenizer;
031:
032: /** This class takes care of HTTP Responses. It sends data back to the browser.
033: */
034: public class HttpResponseImpl implements HttpResponse {
035:
036: /** Response string */
037: private String responseString = "OK";
038:
039: /** Code */
040: private int code = 200;
041:
042: /** Response headers */
043: private final Map<String, String> headers = new HashMap<String, String>();
044:
045: /** Response body */
046: private byte[] body = new byte[0];
047:
048: /** the writer for the response */
049: private transient PrintWriter writer;
050: /** the raw body */
051: private transient ByteArrayOutputStream baos;
052:
053: /** the HTTP version */
054: public static final String HTTP_VERSION = "HTTP/1.1";
055: /** a line feed character */
056: public static final String CRLF = "\r\n";
057: /** a space character */
058: public static final String SP = " ";
059: /** a colon and space */
060: public static final String CSP = ": ";
061: /** the server to send data from */
062: public static String server;
063:
064: private HttpRequestImpl request;
065: private URLConnection content;
066:
067: protected void setRequest(HttpRequestImpl request) {
068: this .request = request;
069: }
070:
071: /** sets a header to be sent back to the browser
072: * @param name the name of the header
073: * @param value the value of the header
074: */
075: public void setHeader(String name, String value) {
076: headers.put(name, value);
077: }
078:
079: /** Gets a header based on the name passed in
080: * @param name The name of the header
081: * @return the value of the header
082: */
083: public String getHeader(String name) {
084: return headers.get(name);
085: }
086:
087: /** Gets the PrintWriter to send data to the browser
088: * @return the PrintWriter to send data to the browser
089: */
090: public PrintWriter getPrintWriter() {
091: return writer;
092: }
093:
094: /** gets the OutputStream to send data to the browser
095: * @return the OutputStream to send data to the browser
096: */
097: public OutputStream getOutputStream() {
098: return baos;
099: }
100:
101: public void flushBuffer() throws IOException {
102: // there is really no way to flush
103: }
104:
105: /** sets the HTTP response code to be sent to the browser. These codes are:
106: *
107: * OPTIONS = 0
108: * GET = 1
109: * HEAD = 2
110: * POST = 3
111: * PUT = 4
112: * DELETE = 5
113: * TRACE = 6
114: * CONNECT = 7
115: * UNSUPPORTED = 8
116: * @param code the code to be sent to the browser
117: */
118: public void setCode(int code) {
119: this .code = code;
120: }
121:
122: /** gets the HTTP response code
123: * @return the HTTP response code
124: */
125: public int getCode() {
126: return code;
127: }
128:
129: /** sets the content type to be sent back to the browser
130: * @param type the type to be sent to the browser (i.e. "text/html")
131: */
132: public void setContentType(String type) {
133: setHeader("Content-Type", type);
134: }
135:
136: /** gets the content type that will be sent to the browser
137: * @return the content type (i.e. "text/html")
138: */
139: public String getContentType() {
140: return getHeader("Content-Type");
141: }
142:
143: /** Sets the response string to be sent to the browser
144: * @param responseString the response string
145: */
146: public void setResponseString(String responseString) {
147: this .responseString = responseString;
148: }
149:
150: /** resets the data to be sent to the browser */
151: public void reset() {
152: initBody();
153: }
154:
155: /** resets the data to be sent to the browser with the response code and response
156: * string
157: * @param code the code to be sent to the browser
158: * @param responseString the response string to be sent to the browser
159: */
160: public void reset(int code, String responseString) {
161: setCode(code);
162: setResponseString(responseString);
163: initBody();
164: }
165:
166: /*------------------------------------------------------------*/
167: /* Methods for writing out a response */
168: /*------------------------------------------------------------*/
169: /** creates a new instance of HttpResponseImpl with default values */
170: protected HttpResponseImpl() {
171: this (200, "OK", "text/html");
172: }
173:
174: /** Creates a new HttpResponseImpl with user provided parameters
175: * @param code the HTTP Response code, see <a href="http://www.ietf.org/rfc/rfc2616.txt">http://www.ietf.org/rfc/rfc2616.txt</a>
176: * for these codes
177: * @param responseString the response string to be sent back
178: * @param contentType the content type to be sent back
179: */
180: protected HttpResponseImpl(int code, String responseString,
181: String contentType) {
182: this .responseString = responseString;
183: this .code = code;
184:
185: // Default headers
186: setHeader("Server", getServerName());
187: setHeader("Connection", "close");
188: setHeader("Content-Type", contentType);
189:
190: // create the body.
191: initBody();
192: }
193:
194: /** Takes care of sending the response line, headers and body
195: *
196: * HTTP/1.1 200 OK
197: * Server: Netscape-Enterprise/3.6 SP3
198: * Date: Thu, 07 Jun 2001 17:30:42 GMT
199: * Content-Type: text/html
200: * Connection: close
201: * @param output the output to send the response to
202: * @throws java.io.IOException if an exception is thrown
203: */
204: protected void writeMessage(OutputStream output) throws IOException {
205: DataOutput out = new DataOutputStream(output);
206: //DataOutput log = new DataOutputStream(System.out);
207: //System.out.println("\nRESPONSE");
208: closeMessage();
209: // writeResponseLine(log);
210: // writeHeaders(log);
211: // writeBody(log);
212: writeResponseLine(out);
213: writeHeaders(out);
214: writeBody(out);
215: }
216:
217: /** initalizes the body */
218: private void initBody() {
219: baos = new ByteArrayOutputStream();
220: writer = new PrintWriter(baos);
221: }
222:
223: /** Creates a string version of the response similar to:
224: *
225: * HTTP/1.1 200 OK
226: * @return the string value of this HttpResponseImpl
227: */
228: public String toString() {
229: StringBuffer buf = new StringBuffer(40);
230:
231: buf.append(HTTP_VERSION);
232: buf.append(SP);
233: buf.append(code);
234: buf.append(SP);
235: buf.append(responseString);
236:
237: return buf.toString();
238: }
239:
240: /** closes the message sent to the browser
241: */
242: private void closeMessage() {
243: setContentLengthHeader();
244: setCookieHeader();
245: }
246:
247: private void setContentLengthHeader() {
248: if (content == null) {
249: writer.flush();
250: writer.close();
251: body = baos.toByteArray();
252: setHeader("Content-Length", body.length + "");
253: } else {
254: setHeader("Content-Length", content.getContentLength() + "");
255: }
256: }
257:
258: private void setCookieHeader() {
259: if (request == null || request.getSession() == null)
260: return;
261:
262: HttpSession session = request.getSession(false);
263:
264: if (session == null)
265: return;
266:
267: StringBuffer cookie = new StringBuffer();
268: cookie.append(HttpRequestImpl.EJBSESSIONID);
269: cookie.append('=');
270: cookie.append(session.getId());
271: cookie.append("; Path=/");
272:
273: headers.put(HttpRequest.HEADER_SET_COOKIE, cookie.toString());
274: }
275:
276: /** Writes a response line similar to this:
277: *
278: * HTTP/1.1 200 OK
279: *
280: * to the browser
281: * @param out the output stream to write the response line to
282: * @throws java.io.IOException if an exception is thrown
283: */
284: private void writeResponseLine(DataOutput out) throws IOException {
285: out.writeBytes(HTTP_VERSION);
286: out.writeBytes(SP);
287: out.writeBytes(code + "");
288: out.writeBytes(SP);
289: out.writeBytes(responseString);
290: out.writeBytes(CRLF);
291: }
292:
293: /** writes the headers out to the browser
294: * @param out the output stream to be sent to the browser
295: * @throws java.io.IOException if an exception is thrown
296: */
297: private void writeHeaders(DataOutput out) throws IOException {
298: for (Map.Entry<String, String> entry : headers.entrySet()) {
299: out.writeBytes("" + entry.getKey());
300: out.writeBytes(CSP);
301: out.writeBytes("" + entry.getValue());
302: out.writeBytes(CRLF);
303: }
304: }
305:
306: /** writes the body out to the browser
307: * @param out the output stream that writes to the browser
308: * @throws java.io.IOException if an exception is thrown
309: */
310: private void writeBody(DataOutput out) throws IOException {
311: out.writeBytes(CRLF);
312: if (content == null) {
313: out.write(body);
314: } else {
315: InputStream in = content.getInputStream();
316: byte buf[] = new byte[1024];
317:
318: int i;
319: while ((i = in.read(buf)) != -1) {
320: out.write(buf, 0, i);
321: }
322: }
323: }
324:
325: /** gets the name of the server being used
326: * @return the name of the server
327: */
328: public String getServerName() {
329: if (server == null) {
330: String version = "???";
331: String os = "(unknown os)";
332:
333: try {
334: Properties versionInfo = new Properties();
335: versionInfo.load(new URL(
336: "resource:/openejb-version.properties")
337: .openConnection().getInputStream());
338: version = versionInfo.getProperty("version");
339: os = System.getProperty("os.name") + "/"
340: + System.getProperty("os.version") + " ("
341: + System.getProperty("os.arch") + ")";
342: } catch (IOException e) {
343: }
344:
345: server = "OpenEJB/" + version + " " + os;
346: }
347: return server;
348: }
349:
350: /** This could be improved at some day in the future
351: * to also include a stack trace of the exceptions
352: * @param message the error message to be sent
353: * @return the HttpResponseImpl that this error belongs to
354: */
355: protected static HttpResponseImpl createError(String message) {
356: return createError(message, null);
357: }
358:
359: /** creates an error with user defined variables
360: * @param message the message of the error
361: * @param t a Throwable to print a stack trace to
362: * @return the HttpResponseImpl that this error belongs to
363: */
364: protected static HttpResponseImpl createError(String message,
365: Throwable t) {
366: HttpResponseImpl res = new HttpResponseImpl(500,
367: "Internal Server Error", "text/html");
368: PrintWriter body = res.getPrintWriter();
369:
370: body.println("<html>");
371: body.println("<body>");
372: body.println("<h3>Internal Server Error</h3>");
373: body.println("<br><br>");
374: System.out.println("ERROR");
375: if (message != null) {
376: StringTokenizer msg = new StringTokenizer(message, "\n\r");
377:
378: while (msg.hasMoreTokens()) {
379: body.print(msg.nextToken());
380: body.println("<br>");
381: }
382: }
383:
384: if (t != null) {
385: try {
386: body.println("<br><br>");
387: body.println("Stack Trace:<br>");
388: ByteArrayOutputStream baos = new ByteArrayOutputStream();
389: PrintWriter writer = new PrintWriter(baos);
390: t.printStackTrace(writer);
391: writer.flush();
392: writer.close();
393: message = new String(baos.toByteArray());
394: StringTokenizer msg = new StringTokenizer(message,
395: "\n\r");
396:
397: while (msg.hasMoreTokens()) {
398: body.print(msg.nextToken());
399: body.println("<br>");
400: }
401: } catch (Exception e) {
402: }
403: }
404:
405: body.println("</body>");
406: body.println("</html>");
407:
408: return res;
409: }
410:
411: /** Creates a forbidden response to be sent to the browser using IP authentication
412: * @param ip the ip that is forbidden
413: * @return the HttpResponseImpl that this error belongs to
414: */
415: protected static HttpResponseImpl createForbidden(String ip) {
416: HttpResponseImpl res = new HttpResponseImpl(403, "Forbidden",
417: "text/html");
418: PrintWriter body = res.getPrintWriter();
419:
420: body.println("<html>");
421: body.println("<body>");
422: body.println("<h3>Forbidden</h3>");
423: body.println("<br><br>");
424: // Add more text here
425: // IP not allowed, etc.
426: body
427: .println("IP address: "
428: + ip
429: + " is not registered on this server, please contact your system administrator.");
430: body.println("</body>");
431: body.println("</html>");
432:
433: return res;
434: }
435:
436: /** writes this object out to a file
437: * @param out the ObjectOutputStream to write to
438: * @throws java.io.IOException if an exception is thrown
439: */
440: private void writeObject(java.io.ObjectOutputStream out)
441: throws IOException {
442: /** Response string */
443: out.writeObject(responseString);
444:
445: /** Code */
446: out.writeInt(code);
447:
448: /** Response headers */
449: out.writeObject(headers);
450:
451: /** Response body */
452: writer.flush();
453: body = baos.toByteArray();
454: //System.out.println("[] body "+body.length );
455: out.writeObject(body);
456: }
457:
458: /** Reads in a serilized HttpResponseImpl object from a file
459: * @param in the input to read the object from
460: * @throws java.io.IOException if an exception is thrown
461: * @throws ClassNotFoundException if an exception is thrown
462: */
463: @SuppressWarnings({"unchecked"})
464: private void readObject(java.io.ObjectInputStream in)
465: throws IOException, ClassNotFoundException {
466: /** Response string */
467: this .responseString = (String) in.readObject();
468:
469: /** Code */
470: this .code = in.readInt();
471:
472: /** Response headers */
473: Map headers = (Map) in.readObject();
474: this .headers.clear();
475: this .headers.putAll(headers);
476:
477: /** Response body */
478: body = (byte[]) in.readObject();
479: //System.out.println("[] body "+body.length );
480: baos = new ByteArrayOutputStream();
481: baos.write(body);
482: writer = new PrintWriter(baos);
483:
484: }
485:
486: /**
487: * @param content The content to set.
488: */
489: public void setContent(URLConnection content) {
490: this .content = content;
491: }
492:
493: public void setStatusCode(int code) {
494: this .setCode(code);
495: }
496:
497: public int getStatusCode() {
498: return this .getCode();
499: }
500:
501: public void setStatusMessage(String responseString) {
502: this.setResponseString(responseString);
503: }
504: }
|