1 /** 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 package org.apache.openejb.webadmin.httpd; 18 19 import java.io.ByteArrayOutputStream; 20 import java.io.DataOutput; 21 import java.io.DataOutputStream; 22 import java.io.IOException; 23 import java.io.InputStream; 24 import java.io.OutputStream; 25 import java.io.PrintWriter; 26 import java.net.URL; 27 import java.net.URLConnection; 28 import java.util.HashMap; 29 import java.util.Iterator; 30 import java.util.Map; 31 import java.util.Properties; 32 import java.util.StringTokenizer; 33 34 import org.apache.openejb.webadmin.HttpRequest; 35 import org.apache.openejb.webadmin.HttpResponse; 36 import org.apache.openejb.webadmin.HttpSession; 37 import org.apache.openejb.util.JarUtils; 38 import org.apache.openejb.util.OpenEjbVersion; 39 40 /** This class takes care of HTTP Responses. It sends data back to the browser. 41 * @author <a href="mailto:david.blevins@visi.com">David Blevins</a> 42 * @author <a href="mailto:tim_urberg@yahoo.com">Tim Urberg</a> 43 */ 44 public class HttpResponseImpl implements HttpResponse { 45 46 /** Response string */ 47 private String responseString = "OK"; 48 49 /** Code */ 50 private int code = 200; 51 52 /** Response headers */ 53 private HashMap headers; 54 55 /** Response body */ 56 private byte[] body = new byte[0]; 57 58 /** the writer for the response */ 59 private transient PrintWriter writer; 60 /** the raw body */ 61 private transient ByteArrayOutputStream baos; 62 63 /** the HTTP version */ 64 public static final String HTTP_VERSION = "HTTP/1.1"; 65 /** a line feed character */ 66 public static final String CRLF = "\r\n"; 67 /** a space character */ 68 public static final String SP = " "; 69 /** a colon and space */ 70 public static final String CSP = ": "; 71 /** the server to send data from */ 72 public static String server; 73 74 private HttpRequestImpl request; 75 private URLConnection content; 76 77 protected void setRequest(HttpRequestImpl request){ 78 this.request = request; 79 } 80 81 /** sets a header to be sent back to the browser 82 * @param name the name of the header 83 * @param value the value of the header 84 */ 85 public void setHeader(String name, String value){ 86 headers.put(name, value); 87 } 88 89 /** Gets a header based on the name passed in 90 * @param name The name of the header 91 * @return the value of the header 92 */ 93 public String getHeader(String name){ 94 return (String) headers.get(name); 95 } 96 97 /** Gets the PrintWriter to send data to the browser 98 * @return the PrintWriter to send data to the browser 99 */ 100 public PrintWriter getPrintWriter(){ 101 return writer; 102 } 103 104 /** gets the OutputStream to send data to the browser 105 * @return the OutputStream to send data to the browser 106 */ 107 public OutputStream getOutputStream(){ 108 return baos; 109 } 110 111 /** sets the HTTP response code to be sent to the browser. These codes are: 112 * 113 * OPTIONS = 0 114 * GET = 1 115 * HEAD = 2 116 * POST = 3 117 * PUT = 4 118 * DELETE = 5 119 * TRACE = 6 120 * CONNECT = 7 121 * UNSUPPORTED = 8 122 * @param code the code to be sent to the browser 123 */ 124 public void setCode(int code){ 125 this.code = code; 126 } 127 128 /** gets the HTTP response code 129 * @return the HTTP response code 130 */ 131 public int getCode(){ 132 return code; 133 } 134 135 /** sets the content type to be sent back to the browser 136 * @param type the type to be sent to the browser (i.e. "text/html") 137 */ 138 public void setContentType(String type){ 139 setHeader("Content-Type", type); 140 } 141 142 /** gets the content type that will be sent to the browser 143 * @return the content type (i.e. "text/html") 144 */ 145 public String getContentType(){ 146 return getHeader("Content-Type"); 147 } 148 149 /** Sets the response string to be sent to the browser 150 * @param responseString the response string 151 */ 152 public void setResponseString(String responseString){ 153 this.responseString = responseString; 154 } 155 156 /** Sets the response string to be sent to the browser 157 * @return the response string 158 */ 159 public String getResponseString(){ 160 return responseString; 161 } 162 163 /** resets the data to be sent to the browser */ 164 public void reset(){ 165 initBody(); 166 } 167 168 /** resets the data to be sent to the browser with the response code and response 169 * string 170 * @param code the code to be sent to the browser 171 * @param responseString the response string to be sent to the browser 172 */ 173 public void reset(int code, String responseString){ 174 setCode(code); 175 setResponseString(responseString); 176 initBody(); 177 } 178 179 /*------------------------------------------------------------*/ 180 /* Methods for writing out a response */ 181 /*------------------------------------------------------------*/ 182 /** creates a new instance of HttpResponseImpl with default values */ 183 protected HttpResponseImpl(){ 184 this(200, "OK", "text/html"); 185 } 186 187 /** Creates a new HttpResponseImpl with user provided parameters 188 * @param code the HTTP Response code, see <a href="http://www.ietf.org/rfc/rfc2616.txt">http://www.ietf.org/rfc/rfc2616.txt</a> 189 * for these codes 190 * @param responseString the response string to be sent back 191 * @param contentType the content type to be sent back 192 */ 193 protected HttpResponseImpl(int code, String responseString, String contentType){ 194 this.responseString = responseString; 195 this.headers = new HashMap(); 196 this.code = code; 197 198 // Default headers 199 setHeader("Server", getServerName()); 200 setHeader("Connection","close"); 201 setHeader("Content-Type",contentType); 202 203 // create the body. 204 initBody(); 205 } 206 207 /** Takes care of sending the response line, headers and body 208 * 209 * HTTP/1.1 200 OK 210 * Server: Netscape-Enterprise/3.6 SP3 211 * Date: Thu, 07 Jun 2001 17:30:42 GMT 212 * Content-Type: text/html 213 * Connection: close 214 * @param output the output to send the response to 215 * @throws IOException if an exception is thrown 216 */ 217 protected void writeMessage(OutputStream output) throws IOException{ 218 DataOutput out = new DataOutputStream(output); 219 DataOutput log = new DataOutputStream(System.out); 220 //System.out.println("\nRESPONSE"); 221 closeMessage(); 222 //writeResponseLine(log); 223 // writeHeaders(log); 224 // writeBody(log); 225 writeResponseLine(out); 226 writeHeaders(out); 227 writeBody(out); 228 } 229 230 /** initalizes the body */ 231 private void initBody(){ 232 baos = new ByteArrayOutputStream(); 233 writer = new PrintWriter( baos ); 234 } 235 236 /** Creates a string version of the response similar to: 237 * 238 * HTTP/1.1 200 OK 239 * @return the string value of this HttpResponseImpl 240 */ 241 public String toString(){ 242 StringBuffer buf = new StringBuffer(40); 243 244 buf.append(HTTP_VERSION); 245 buf.append(SP); 246 buf.append(code+""); 247 buf.append(SP); 248 buf.append(responseString); 249 250 return buf.toString(); 251 } 252 253 /** closes the message sent to the browser 254 * @throws IOException if an exception is thrown 255 */ 256 private void closeMessage() throws IOException{ 257 setContentLengthHeader(); 258 setCookieHeader(); 259 } 260 261 262 private void setContentLengthHeader() { 263 if (content == null){ 264 writer.flush(); 265 writer.close(); 266 body = baos.toByteArray(); 267 setHeader("Content-Length", body.length+""); 268 } else { 269 setHeader("Content-Length", content.getContentLength()+""); 270 } 271 } 272 273 private void setCookieHeader() { 274 if (request == null || request.getSession() == null) return; 275 276 HttpSession session = request.getSession(false); 277 278 if (session == null) return; 279 280 StringBuffer cookie = new StringBuffer(); 281 cookie.append(HttpRequestImpl.EJBSESSIONID); 282 cookie.append('='); 283 cookie.append(session.getId()); 284 cookie.append("; Path=/"); 285 286 headers.put(HttpRequest.HEADER_SET_COOKIE, cookie.toString()); 287 } 288 289 /** Writes a response line similar to this: 290 * 291 * HTTP/1.1 200 OK 292 * 293 * to the browser 294 * @param out the output stream to write the response line to 295 * @throws IOException if an exception is thrown 296 */ 297 private void writeResponseLine(DataOutput out) throws IOException{ 298 out.writeBytes(HTTP_VERSION); 299 out.writeBytes(SP); 300 out.writeBytes(code+""); 301 out.writeBytes(SP); 302 out.writeBytes(responseString); 303 out.writeBytes(CRLF); 304 } 305 306 /** writes the headers out to the browser 307 * @param out the output stream to be sent to the browser 308 * @throws IOException if an exception is thrown 309 */ 310 private void writeHeaders(DataOutput out) throws IOException{ 311 Iterator it = headers.entrySet().iterator(); 312 313 while (it.hasNext()){ 314 Map.Entry entry = (Map.Entry)it.next(); 315 out.writeBytes(""+entry.getKey()); 316 out.writeBytes(CSP); 317 out.writeBytes(""+entry.getValue()); 318 out.writeBytes(CRLF); 319 } 320 } 321 322 /** writes the body out to the browser 323 * @param out the output stream that writes to the browser 324 * @throws IOException if an exception is thrown 325 */ 326 private void writeBody(DataOutput out) throws IOException{ 327 out.writeBytes(CRLF); 328 if (content == null){ 329 out.write(body); 330 } else { 331 InputStream in = content.getInputStream(); 332 byte buf[] = new byte[1024]; 333 for(int i = 0; (i = in.read(buf)) != -1; out.write(buf, 0, i)); 334 } 335 } 336 337 /** gets the name of the server being used 338 * @return the name of the server 339 */ 340 public String getServerName(){ 341 if (server == null) { 342 String version = "???"; 343 String os = "(unknown os)"; 344 345 OpenEjbVersion openejbInfo = OpenEjbVersion.get(); 346 347 version = openejbInfo.getVersion(); 348 os = System.getProperty("os.name")+"/"+System.getProperty("os.version")+" ("+System.getProperty("os.arch")+")"; 349 350 server = "OpenEJB/" +version+ " "+os; 351 } 352 return server; 353 } 354 355 356 /** This could be improved at some day in the future 357 * to also include a stack trace of the exceptions 358 * @param message the error message to be sent 359 * @return the HttpResponseImpl that this error belongs to 360 */ 361 protected static HttpResponseImpl createError(String message){ 362 return createError(message, null); 363 } 364 365 /** creates an error with user defined variables 366 * @param message the message of the error 367 * @param t a Throwable to print a stack trace to 368 * @return the HttpResponseImpl that this error belongs to 369 */ 370 protected static HttpResponseImpl createError(String message, Throwable t){ 371 HttpResponseImpl res = new HttpResponseImpl(500, "Internal Server Error", "text/html"); 372 java.io.PrintWriter body = res.getPrintWriter(); 373 374 body.println("<html>"); 375 body.println("<body>"); 376 body.println("<h3>Internal Server Error</h3>"); 377 body.println("<br><br>"); 378 379 if (message != null) { 380 StringTokenizer msg = new StringTokenizer(message, "\n\r"); 381 382 while (msg.hasMoreTokens()) { 383 body.print( msg.nextToken() ); 384 body.println("<br>"); 385 } 386 } 387 388 if (t != null) { 389 try{ 390 body.println("<br><br>"); 391 body.println("Stack Trace:<br>"); 392 ByteArrayOutputStream baos = new ByteArrayOutputStream(); 393 PrintWriter writer = new PrintWriter( baos ); 394 t.printStackTrace(writer); 395 writer.flush(); 396 writer.close(); 397 message = new String(baos.toByteArray()); 398 StringTokenizer msg = new StringTokenizer(message, "\n\r"); 399 400 while (msg.hasMoreTokens()) { 401 body.print( msg.nextToken() ); 402 body.println("<br>"); 403 } 404 } catch (Exception e){ 405 } 406 } 407 408 body.println("</body>"); 409 body.println("</html>"); 410 411 return res; 412 } 413 414 /** Creates a forbidden response to be sent to the browser using IP authentication 415 * @param ip the ip that is forbidden 416 * @return the HttpResponseImpl that this error belongs to 417 */ 418 protected static HttpResponseImpl createForbidden(String ip){ 419 HttpResponseImpl res = new HttpResponseImpl(403, "Forbidden", "text/html"); 420 java.io.PrintWriter body = res.getPrintWriter(); 421 422 body.println("<html>"); 423 body.println("<body>"); 424 body.println("<h3>Forbidden</h3>"); 425 body.println("<br><br>"); 426 // Add more text here 427 // IP not allowed, etc. 428 body.println("IP address: " + ip + " is not registered on this server, please contact your system administrator."); 429 body.println("</body>"); 430 body.println("</html>"); 431 432 return res; 433 } 434 435 /** writes this object out to a file 436 * @param out the ObjectOutputStream to write to 437 * @throws IOException if an exception is thrown 438 */ 439 private void writeObject(java.io.ObjectOutputStream out) throws IOException{ 440 /** Response string */ 441 out.writeObject( responseString ); 442 443 /** Code */ 444 out.writeInt( code ); 445 446 /** Response headers */ 447 out.writeObject( headers ); 448 449 /** Response body */ 450 writer.flush(); 451 body = baos.toByteArray(); 452 //System.out.println("[] body "+body.length ); 453 out.writeObject( body ); 454 } 455 456 /** Reads in a serilized HttpResponseImpl object from a file 457 * @param in the input to read the object from 458 * @throws IOException if an exception is thrown 459 * @throws ClassNotFoundException if an exception is thrown 460 */ 461 private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{ 462 /** Response string */ 463 this.responseString = (String)in.readObject(); 464 465 /** Code */ 466 this.code = in.readInt(); 467 468 /** Response headers */ 469 this.headers = (HashMap) in.readObject(); 470 471 /** Response body */ 472 body = (byte[]) in.readObject(); 473 //System.out.println("[] body "+body.length ); 474 baos = new ByteArrayOutputStream(); 475 baos.write( body ); 476 writer = new PrintWriter( baos ); 477 478 } 479 /** 480 * @param content The content to set. 481 */ 482 public void setContent(URLConnection content) { 483 this.content = content; 484 } 485 486 }