All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sunlabs.brazil.server.Request Maven / Gradle / Ivy

The newest version!
/*
 * Request.java
 *
 * Brazil project web application toolkit,
 * export version: 2.3 
 * Copyright (c) 1998-2007 Sun Microsystems, Inc.
 *
 * Sun Public License Notice
 *
 * The contents of this file are subject to the Sun Public License Version 
 * 1.0 (the "License"). You may not use this file except in compliance with 
 * the License. A copy of the License is included as the file "license.terms",
 * and also available at http://www.sun.com/
 * 
 * The Original Code is from:
 *    Brazil project web application toolkit release 2.3.
 * The Initial Developer of the Original Code is: suhler.
 * Portions created by suhler are Copyright (C) Sun Microsystems, Inc.
 * All Rights Reserved.
 * 
 * Contributor(s): cstevens, drach, rinaldo, suhler.
 *
 * Version:  2.11
 * Created by suhler on 98/09/14
 * Last modified by suhler on 07/03/07 09:15:59
 *
 * Version Histories:
 *
 * 2.11 07/03/07-09:15:59 (suhler)
 *   added log messages for post failures
 *
 * 2.10 06/11/13-14:17:10 (suhler)
 *   tweak error message
 *
 * 2.9 06/04/28-16:16:26 (suhler)
 *   don't send content for "204" results
 *   .
 *
 * 2.8 06/01/17-09:42:21 (suhler)
 *   doc fixes
 *
 * 2.7 05/12/08-13:03:19 (suhler)
 *   protect against DOS from bogus http requests
 *
 * 2.6 04/11/30-15:19:41 (suhler)
 *   fixed sccs version string
 *
 * 2.5 04/11/03-08:35:16 (suhler)
 *   set "url.orig" in request
 *
 * 2.4 03/08/01-16:17:47 (suhler)
 *   fixes for javadoc
 *
 * 2.3 03/05/12-16:27:37 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/mack.eng/export/ws/brazil/naws".
 *
 * 1.71.1.1 03/05/12-16:24:16 (suhler)
 *   added a "serverProtocol" variable to allow handlers to change the
 *   protocol (e.g. http) the server claims to be using on a per-request basis.
 *   If "serverProtocol" is unchanged (the default), then the Server.protocol
 *   is used instead.
 *   This is useful when fronting Brazil servers with ssl gateways; the protocol
 *   can be changed from "http" to "https" to allow redirects to work
 *   properly
 *
 * 2.2 02/10/18-15:04:49 (drach)
 *   Add shared props after Request.props rather than before server.props
 *
 * 2.1 02/10/01-16:34:52 (suhler)
 *   version change
 *
 * 1.71 02/08/21-18:25:46 (suhler)
 *   doc update
 *
 * 1.70 02/06/27-18:38:47 (suhler)
 *   don't override content type if its already been set
 *
 * 1.69 02/05/07-07:04:18 (drach)
 *   Add no arg constructor.
 *
 * 1.68 02/04/23-14:02:36 (suhler)
 *   bug fix exposed by tests
 *
 * 1.67 02/04/18-11:17:23 (suhler)
 *   add processing for "HEAD" requests in sendResponse(),  This might not
 *   cover all the cases yet
 *
 * 1.66 02/02/07-14:06:10 (suhler)
 *   sendResponse() now does the "right think" for HEAD requests
 *
 * 1.65 01/09/13-09:26:22 (suhler)
 *   remove uneeded import
 *
 * 1.64 01/08/22-16:23:07 (drach)
 *   Change comments regarding shared properties.
 *
 * 1.63 01/08/21-10:59:16 (suhler)
 *   add check for maximum request length
 *
 * 1.62 01/08/20-17:32:21 (suhler)
 *   change readFully() to read()
 *
 * 1.61 01/08/13-09:09:29 (drach)
 *   Change server.props back to Properties object.
 *
 * 1.60 01/08/07-14:19:30 (drach)
 *   Move PropertiesList debug initialization to earliest possible point.
 *
 * 1.59 01/08/03-15:29:31 (drach)
 *   Remove SharedProps and add PropertiesList
 *
 * 1.58 01/07/10-10:37:58 (drach)
 *   Account for semantic change for equals() in Dictionary objects.
 *
 * 1.57 01/06/05-22:14:06 (drach)
 *   Reduce access control for brazil.servlet package
 *
 * 1.56 01/05/30-08:33:43 (drach)
 *   Reduce access control on selected fields and methods.
 *
 * 1.55 01/05/24-19:53:51 (drach)
 *   Count all bytes written.
 *
 * 1.54 01/03/12-17:42:20 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.52.1.1 01/03/12-17:29:31 (cstevens)
 *   Change log() methods to make testing easier.
 *
 * 1.53 01/03/06-11:16:47 (suhler)
 *   chage Vector.add() to Vector.addElement() for compatibility with java 1.1
 *
 * 1.52 01/03/05-16:21:46 (cstevens)
 *   test suite:
 *   Timeout during request gave error "null 408 Timeout".  Should be
 *   "HTTP/1.1 408 Timeout"
 *
 * 1.51 01/02/20-16:29:11 (cstevens)
 *   docs
 *
 * 1.50 01/02/20-16:16:24 (cstevens)
 *   docs.
 *
 * 1.49 01/02/20-15:52:32 (cstevens)
 *   Shared Dictionaries are searched after request.props and before server.props.
 *
 * 1.48 01/02/12-17:15:02 (cstevens)
 *   Get rid of Request.RechainableProperties public inner class.
 *   Add method to Request to add an existing Properties object to a request w/o
 *   exposing a special class or requiring all users of Request to use a special
 *   class.  Works for multithreaded sharing of the same end-user provided
 *   Properties object also, which the RechainableProperties didn't do.
 *
 * 1.47 00/12/05-13:12:36 (suhler)
 *   added getStatus() and getReuseCount() methods for statistics gathering
 *   - changed semantics of bytesWritten: resets at each request
 *
 * 1.46 00/11/20-13:22:13 (suhler)
 *   doc fixes
 *
 * 1.45 00/11/15-09:50:35 (suhler)
 *   added start-of-request timestamp
 *
 * 1.44 00/10/05-09:18:48 (suhler)
 *   remove dead code
 *
 * 1.43 00/07/07-17:02:18 (suhler)
 *   remove System.out.println(s)
 *
 * 1.42 00/07/06-15:49:29 (suhler)
 *   doc update
 *
 * 1.41 00/06/29-10:47:15 (suhler)
 *   HttpOutputStream counts the # of bytes written
 *
 * 1.40 00/05/31-13:51:32 (suhler)
 *   docs
 *
 * 1.39 00/05/15-12:02:23 (suhler)
 *   remove dependency on regexp package by putting in ugly code
 *
 * 1.38 00/03/29-16:43:43 (cstevens)
 *   Request was missing documentation of basic functionality.
 *
 * 1.37 00/03/10-17:11:33 (cstevens)
 *   Eliminated DataOutputStream from Request.java.
 *   Added a rechainable Properties object to Request.java
 *
 * 1.36 99/11/16-14:35:29 (cstevens)
 *   Request.java:
 *   1. If the "Content-Length" provided in the request was malformed, the
 *   log message should display how it was malformed.
 *   2. If Request.sendError() was called, it was logging the word "null"
 *   if the user passed in null for the detail message.  Aesthetically
 *   displeasing.
 *   3. If Request.sendError() is called when part of a response has already
 *   been sent, it cannot send the error headers and error message, because
 *   that would confuse things, so it must just close the connection.  That
 *   was done right, but it wasn't bumping the Server.errorCount.  It still
 *   should do that always.
 *
 * 1.35 99/11/03-17:52:31 (cstevens)
 *   MultiHostHandler.
 *
 * 1.34 99/10/26-18:54:35 (cstevens)
 *   Eliminate public methods Server.initHandler() and Server.initObject().
 *   Get rid of public variables Request.server and Request.sock:
 *   A. In all cases, Request.server was not necessary; it was mainly used for
 *   constructing the absolute URL for a redirect, so Request.redirect() was
 *   rewritten to take an absolute or relative URL and do the right thing.
 *   B. Request.sock was changed to Request.getSock(); it is still rarely used
 *   for diagnostics and logging (e.g., ChainSawHandler).
 *   HTTP request returned ungracefully if "Content-Length" in a POST was
 *   negative or too much to allocate.
 *
 * 1.33 99/10/25-15:48:03 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.31.1.1 99/10/25-15:38:48 (cstevens)
 *   String.equals() can take null.
 *
 * 1.32 99/10/25-14:32:44 (suhler)
 *   missing content length
 *
 * 1.25.2.1 99/10/23-19:49:32 (rinaldo)
 *
 * 1.31 99/10/19-18:57:53 (cstevens)
 *   toString
 *
 * 1.30 99/10/14-13:14:59 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.28.1.3 99/10/14-13:11:04 (cstevens)
 *   @author & @version
 *
 * 1.28.1.2 99/10/14-12:54:36 (cstevens)
 *   don't display socket information twice when logging.
 *   rewrite sendResponse() to take InputStream rather than HttpInputStream.
 *   Maybe this is a bad idea.
 *
 * 1.29 99/10/11-12:36:25 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/mack.eng/export/ws/brazil/naws".
 *
 * 1.27.1.1 99/10/11-12:32:00 (suhler)
 *   we were getting duplicate mime headers
 *
 * 1.28.1.1 99/10/08-16:53:26 (cstevens)
 *   Handle malformed URLs that have spaces in the name.  These type of URLS are
 *   sent by RealAudio clients.  HTTP request line is actually simpler now.
 *
 * 1.28 99/10/07-13:01:25 (cstevens)
 *   javadoc lint
 *   accessor methods for setting an integer mime header.
 *
 * 1.27 99/10/04-16:05:37 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/export/ws/brazil/naws".
 *
 * 1.26 99/10/01-11:41:52 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/mack.eng/export/ws/brazil/naws".
 *
 * 1.24.1.2 99/10/01-11:40:05 (suhler)
 *   no query string == "" not null
 *
 * 1.25.1.1 99/10/01-11:27:04 (cstevens)
 *   Change logging to show prefix of Handler generating the log message.
 *
 * 1.25 99/09/30-10:45:43 (cstevens)
 *   Multiple instances of "Server" and "Date" in response: fixed.
 *
 * 1.24.1.1 99/09/22-16:02:49 (suhler)
 *   commented out debugging code
 *
 * 1.24 99/09/15-14:51:24 (cstevens)
 *   import *;
 *
 * 1.23 99/09/15-14:41:24 (cstevens)
 *   Rewritign http server to make it easier to proxy requests.
 *
 * 1.22 99/09/15-13:32:29 (cstevens)
 *   Rewriting http server to allow easier proxying.
 *
 * 1.21 99/08/17-14:50:33 (suhler)
 *   Added convenience method for sending binary data as a response
 *
 * 1.20 99/07/30-10:52:14 (suhler)
 *   always flush after emitting headers
 *   This is used by the tailHandler as a "signal" that all the headers
 *   have been output
 *
 * 1.19 99/06/29-14:34:16 (suhler)
 *   Added public serverUrl method to return a url suitable for redirects
 *   back to the same server
 *   .
 *
 * 1.18 99/06/28-10:53:39 (suhler)
 *   added getServer() method
 *
 * 1.17 99/04/08-09:52:21 (suhler)
 *   Make sure "responseCode" is correct after request is complete
 *
 * 1.16 99/04/07-13:03:18 (suhler)
 *   make responseCode public (why not?)
 *   .
 *
 * 1.15 99/04/01-09:06:02 (suhler)
 *   - Allow PUT requests
 *   - Added 2 more http return codes to support PUT
 *
 * 1.14 99/03/30-09:42:03 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/smartcard.eng/export/ws/brazil/naws".
 *
 * 1.11.1.1 99/03/30-09:26:27 (suhler)
 *   - documentation updates
 *   - fixed non-initialized bug in "connection" headers and request type
 *
 * 1.13 99/03/23-17:21:43 (cstevens)
 *   resolve wildcard imports.
 *
 * 1.12 99/03/23-15:11:28 (cstevens)
 *   Issuing an illegal HTTP request to brazil server resulted in HTTP error
 *   response: "null 400 Bad Request" instead of "HTTP/1.0 400 Bad Request"
 *
 * 1.11 99/03/09-11:27:23 (suhler)
 *   Merged changes between child workspace "/home/suhler/brazil/naws" and
 *   parent workspace "/net/smartcard.eng/export/ws/brazil/naws".
 *
 * 1.9.1.1 99/03/09-11:22:36 (suhler)
 *   handle 100 continue
 *
 * 1.10 99/02/17-17:27:41 (cstevens)
 *   Merged changes between child workspace "/home/cstevens/ws/brazil/naws" and
 *   parent workspace "/net/smartcard.eng/export/ws/brazil/naws".
 *
 * 1.6.1.1 99/02/17-17:17:26 (cstevens)
 *   Brazil server has never correctly handled "%XX" in the url or query data.  It
 *   treated it as a decimal number.
 *
 * 1.9 99/02/10-10:36:01 (suhler)
 *   - changed mime processing to allow un-collapsing of headers
 *   - made a couple of private variables public, for the proxyHandler
 *
 * 1.8 99/02/03-14:12:34 (suhler)
 *   removed superplusous output
 *
 * 1.7 99/01/29-11:48:44 (suhler)
 *   redo connection headers
 *
 * 1.6 98/11/18-13:42:46 (suhler)
 *   The already removed keep-alive stuff creapt back in some how
 *
 * 1.5 98/11/16-16:04:03 (suhler)
 *   Added flag to turn off keep-alives for http/1.0
 *   ./
 *   .
 *
 * 1.4 98/10/27-14:26:30 (suhler)
 *   Added constructor to getQueryData to take pre-existing hash table
 *
 * 1.3 98/10/13-12:05:27 (suhler)
 *   typo
 *
 * 1.2 98/09/21-14:51:36 (suhler)
 *   changed the package names
 *
 * 1.2 98/09/14-18:03:09 (Codemgr)
 *   SunPro Code Manager data about conflicts, renames, etc...
 *   Name history : 2 1 server/Request.java
 *   Name history : 1 0 Request.java
 *
 * 1.1 98/09/14-18:03:08 (suhler)
 *   date and time created 98/09/14 18:03:08 by suhler
 *
 */

package sunlabs.brazil.server;

import sunlabs.brazil.properties.PropertiesList;
import sunlabs.brazil.util.http.HttpInputStream;
import sunlabs.brazil.util.http.HttpUtil;
import sunlabs.brazil.util.http.MimeHeaders;

import java.io.InputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FilterOutputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Dictionary;
import java.util.NoSuchElementException;
import java.util.Hashtable;
import java.util.Properties;
import java.util.StringTokenizer;

/**
 * Represents an HTTP transaction.   A new instance is created
 * by the server for each connection.
 * 

* Provides a set of accessor functions to fetch the individual fields * of the HTTP request. *

* Utility methods that are generically useful for manipulating HTTP * requests are included here as well. An instance of this class is * passed to handlers. There will be exactly one request object per thead * at any time. *

* The fields * {@link #headers}, * {@link #query}, and * {@link #url}, and the method * {@link #getQueryData()} * are most often used to examine the content of the request. * The field * {@link #props} * contains information about the server, or up-stream handlers. *

* The methods * {@link #sendResponse(String, String, int)} and * {@link Request#sendError(int, String)} * are commonly used to return content to the client. The methods * {@link #addHeader(String)} and * {@link #setStatus(int)} can be used to modify the response headers * and return code respectively before the response is sent. *

* Many of the other methods are used internally, but can be useful to * handlers that need finer control over the output that the above methods * provide. Note that the order of the methods is important. For instance, * the user cannot change the HTTP response headers (by calling the * addHeader method or by modifying the * responseHeaders field) after having already sent an HTTP * response. *

* A number of the fields in the Request object are public, * by design. Many of the methods are convenience methods; the underlying * data fields are meant to be accessed for more complicated operations, * such as changing the URL or deleting HTTP response headers. * * @see Handler * @see Server * * @author Stephen Uhler ([email protected]) * @author Colin Stevens ([email protected]) * @version 2.11 */ public class Request { /** * Maximum number of blank lines allowed between requests before * aborting the connecion. The spec allows 0, but some clients add * one or more. */ public static final int MAX_BLANKS = 10; /** * The server that initiated this request. Only under rare circumstances * should this be modified. */ public Server server; /** * Our connection to the client. Only under rare circumstances would this * need to be modified. */ public Socket sock; protected HttpInputStream in; /** * A set of properties local to this request. The property is wrapped * in a PropertiesList object and initially is the head * of a linked list of properties that are searched in order. * This is useful for handlers that wish to communicate via properties * to down-stream handlers, such as modifying a server property for a * particular request. Some handlers may even add entire new sets of * properties onto the front of request.props to temporarily * modify the properties seen by downstream handlers. */ public PropertiesList props; /** * A PropertiesList object that wraps * server.props. When this request is * created, a new PropertiesList wrapping * server.props is created and added to a list consisting * only of props and serverProps. */ public PropertiesList serverProps; /** * The HTTP response to the client is written to this stream. Normally * the convenience methods, such as sendResponse, are used * to send the response, but this field is available if a handler * needs to generate the response specially. *

* If the user chooses to write the response directly to this stream, the * user is still encouraged to use the convenience methods, such as * sendHeaders, to first send the HTTP response headers. * The {@link sunlabs.brazil.filter.FilterHandler} * examines the HTTP response headers * set by the convenience methods to determine whether to filter the * output. *

* Note that the HTTP response headers will not automatically be * sent as a side effect if the user writes to this stream. The user * would either need to call the convenience method * sendHeaders or need to generate the HTTP response headers * themselves. *

* This variable is declared as a Request.HttpOutputStream, * which provides the convenience method writeBytes to write * the byte representation of a string back to the client. If the user * does not need this functionality, this variable may be accessed * simply as a normal OutputStream. * * @see #sendResponse(String, String, int) * @see #sendHeaders(int, String, int) */ public HttpOutputStream out; /* * How many requests this Request will handle. If this goes * to 0, then the connection will be closed even if * keepAlive is true. */ protected int requestsLeft; //----------------------------------------------------------------------- /** * The HTTP request method, such as "GET", "POST", or "PUT". */ public String method; /** * The URL specified in the request, not including any "?" query * string. *

NOTE: Traditionally handlers modify this as needed. The request * property "url.orig" is set to match the url at creation time, and * should be considered "Read only", for those cases where the original * url is required. */ public String url; /** * The query string specified after the URL, or "" if no * query string was specified. */ public String query; /** * The HTTP protocol specified in the request, either "HTTP/1.0" or * "HTTP/1.1". * * @see #version */ public String protocol; /** * Derived from {@link #protocol}, the version of the HTTP protocol * used for this request. Either 10 for "HTTP/1.0" or * 11 for "HTTP/1.1". */ public int version; /** * The HTTP request headers. Keys and values in this table correspond * the field names and values from each line in the HTTP header; * field names are case-insensitive, but the case of the values is * preserved. The order of entries in this table corresponds to the * order in which the request headers were seen. Multiple header lines * with the same key are stored as separate entries in the table. */ public MimeHeaders headers; /** * The uploaded content of this request, usually from a POST. Set to * null if the request has no content. */ public byte[] postData; /** * true if the client requested a persistent connection, * false otherwise. Derived from the {@link #protocol} and * the {@link #headers}, *

* When "Keep-Alive" is requested, the client can issue multiple, * consecutive requests via a single socket connection. By default:

    *
  • HTTP/1.0 requests are not Keep-Alive, unless the * "Connection: Keep-Alive" header was present. *
  • HTTP/1.1 requests are Keep-Alive, unless the "Connection: close" * header was present. *
* The user can change this value from true to * false to forcefully close the connection to the client * after sending the response. The user can change this value from * false to true if the client is using a * different header to request a persistent connection. See * {@link #connectionHeader}. *

* Regardless of this value, if an error is detected while receiving * or responding to an HTTP request, the connection will be closed. */ public boolean keepAlive; /** * The header "Connection" usually controls whether the client * connection will be of type "Keep-Alive" or "close". The same * header is written back to the client in the response headers. *

* The field {@link #keepAlive} is set based on the value of the * "Connection" header. However, not all clients use "Connection" * to request that the connection be kept alive. For instance (although * it does not appear in the HTTP/1.0 or HTTP/1.1 documentation) both * Netscape and IE use the "Proxy-Connection" header when issuing * requests via an HTTP proxy. If a Handler is written to * respond to HTTP proxy requests, it should set keepAlive * depending on the value of the "Proxy-Connection" header, and set * connectionHeader to "Proxy-Connection", since the * convenience methods like setResponse() use these fields * when constructing the response. The server does not handle the * "Proxy-Connection" header by default, since trying to pre-anticipate * all the exceptions to the specification is a "slippery slope". */ public String connectionHeader; /** * This is the server's protocol. It is normally null, but * may be overriden to change the protocol on a per-request * basis. If not set then server.protocol should be used instead. */ public String serverProtocol; //----------------------------------------------------------------------- protected int statusCode; protected String statusPhrase; /** * The HTTP response headers. Keys and values in this table correspond * to the HTTP headers that will be written back to the client when * the response is sent. The order of entries in this table corresponds * to the order in which the HTTP headers will be sent. Multiple header * lines with the same key will be stored as separate entries in the * table. * * @see #addHeader(String, String) */ public MimeHeaders responseHeaders; /* * True if the headers have already been sent, so that if sendError() * is called in the middle of sending a response, it won't send the * headers again, but will cause the connection to be closed afterwards. */ protected boolean headersSent; /** * Time stamp for start of this request - set, but not used. */ public long startMillis; /** * Create a new http request. Requests are created by the server for * use by handlers. * * @param server * The server that owns this request. * * @param sock * The socket of the incoming HTTP request. */ protected Request(Server server, Socket sock) { this.server = server; this.sock = sock; try { in = new HttpInputStream( new BufferedInputStream(sock.getInputStream())); out = new HttpOutputStream( new BufferedOutputStream(sock.getOutputStream())); } catch (IOException e) { /* * Logically we shouldn't get an error obtaining the streams from * the socket, but if it does, it will be caught later as a * NullPointerException by the Connection.run() method the first * time we attempt to read from the socket. */ } requestsLeft = server.maxRequests; keepAlive = true; headers = new MimeHeaders(); responseHeaders = new MimeHeaders(); serverProtocol = null; } /** * Needed by VelocityFilter.Vrequest. Should not be used to create * a Request object. */ protected Request() {} /** * Returns a string representation of this Request. * The string representation is the first line (the method line) of the * HTTP request that this Request is handling. Useful for * debugging. * * @return The string representation of this Request. */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append(method).append(' ').append(url); if ((query != null) && (query.length() > 0)) { sb.append('?').append(query); } sb.append(' ').append(protocol); sb.append(" (").append(sock.toString()).append(")"); return sb.toString(); } /** * Reads an HTTP request from the socket. * * @return true if the request was successfully read and * parsed, false if the request was malformed. * * @throws IOException * if there was an IOException reading from the socket. See * the socket documentation for a description of socket * exceptions. */ public boolean getRequest() throws IOException { if (server.props.get("debugProps") != null) { if (props != null) { props.dump(false, "at beginning of getRequest"); } } /* * Reset state. */ requestsLeft--; connectionHeader = "Connection"; /* I don't think we need to do this while ((props = serverProps.getPrior()) != null) { props.remove(); } */ method = null; url = null; query = null; protocol = "HTTP/1.1"; headers.clear(); postData = null; statusCode = 200; statusPhrase = "OK"; responseHeaders.clear(); startMillis = System.currentTimeMillis(); out.bytesWritten=0; serverProtocol=null; /* * Get first line of HTTP request (the method line). */ String line = null; int count = 0; while (count++ < MAX_BLANKS) { line = in.readLine(MimeHeaders.MAX_LINE); if (line == null) { return false; } else if (line.length() > 0) { break; } log(Server.LOG_INFORMATIONAL, "Skipping blank line"); } if (count >= MAX_BLANKS) { throw new IOException("Too many leading blanks in HTTP request"); } log(Server.LOG_LOG, "Request " + requestsLeft + " " + line); try { StringTokenizer st = new StringTokenizer(line); method = st.nextToken(); url = st.nextToken(); protocol = st.nextToken(); } catch (NoSuchElementException e) { sendError(400, line, null); return false; } /* if ((method.equals("GET") == false) && (method.equals("POST") == false) && (method.equals("PUT") == false)) { sendError(501, method, null); return false; } */ if (protocol.equals("HTTP/1.0")) { version = 10; } else if (protocol.equals("HTTP/1.1")) { version = 11; } else { sendError(505, line, null); return false; } /* * Separate query string from URL. */ int index = url.indexOf('?'); if (index >= 0) { query = url.substring(index + 1); url = url.substring(0, index); } else { query = ""; } headers.read(in); /* * Remember POST data. "Transfer-Encoding: chunked" is not handled * yet. */ String str; str = getRequestHeader("Content-Length"); if (str != null) { int len; try { len=Integer.parseInt(str); if (len > server.maxPost) { log(Server.LOG_DIAGNOSTIC, "Request", "too much post data"); sendError(413, len + " bytes is too much data to post", null); return false; } postData = new byte[len]; } catch (Exception e) { sendError(411, str, null); return false; } catch (OutOfMemoryError e) { log(Server.LOG_DIAGNOSTIC, "Request", "out of memory for post data"); sendError(413, str, null); return false; } log(Server.LOG_DIAGNOSTIC, "Request", "Reading content: " + str); in.readFully(postData); } str = getRequestHeader(connectionHeader); if ("Keep-Alive".equalsIgnoreCase(str)) { keepAlive = true; } else if ("close".equalsIgnoreCase(str)) { keepAlive = false; } else if (version > 10) { keepAlive = true; } else { keepAlive = false; } /* * Delay initialization until we know we need these things */ serverProps = new PropertiesList(server.props); props = new PropertiesList(); props.addBefore(serverProps); /* * Keep track of the original url. This is backwards (for * historical reasons */ props.put("url.orig", url); return true; } boolean shouldKeepAlive() { return (requestsLeft > 0) && keepAlive; } /** * The socket from which the HTTP request was received, and to where the * HTTP response will be written. The user should not directly read from * or write to this socket. The socket is provided other purposes, for * example, imagine a handler that provided different content depending * upon the IP address of the client. * * @return The client socket that issued this HTTP request. */ public Socket getSocket() { return sock; } /** * Logs a message by calling Server.log. Typically a * message is generated on the console or in a log file, if the * level is less than the current server log setting. * * @param level * The severity of the message. * * @param message * The message that will be logged. * * @see Server#log(int, Object, String) */ public void log(int level, String message) { log(level, null, message); } /** * Logs a message by calling Server.log. Typically a * message is generated on the console or in a log file, if the * level is less than the current server log setting. * * @param level * The severity of the message. * * @param obj * The object that the message relates to. * * @param message * The message that will be logged. * * @see Server#log(int, Object, String) */ public void log(int level, Object obj, String message) { server.log(level, obj, message); } /* *----------------------------------------------------------------------- * Request methods. *----------------------------------------------------------------------- */ /** * Returns the value that the given case-insensitive key maps to * in the HTTP request headers. In order to do fancier things like * changing or deleting an existing request header, the user may directly * access the headers field. * * @param key * The key to look for in the HTTP request headers. May not * be null. * * @return The value to which the given key is mapped, or * null if the key is not in the headers. * * @see #headers */ public String getRequestHeader(String key) { return headers.get(key); } /** * Retrieves the query data as a hashtable. * This includes both the query information included as part of the url * and any posted "application/x-www-form-urlencoded" data. * * @param table * An existing hashtable in which to put the query data as * name/value pairs. May be null, in which case * a new hashtable is allocated. * * @return The hashtable in which the query data was stored. */ public Hashtable getQueryData(Hashtable table) { if (table == null) { table = new Hashtable(); } HttpUtil.extractQuery(query, table); if (postData != null) { String contentType = headers.get("Content-Type"); if ("application/x-www-form-urlencoded".equals(contentType)) { HttpUtil.extractQuery(new String(postData), table); } } return table; } /** * Retrieves the query data as a hashtable. * This includes both the query information included as part of the url * and any posted "application/x-www-form-urlencoded" data. * * @return The hashtable in which the query data was stored. */ public Hashtable getQueryData() { return getQueryData(null); } /* *----------------------------------------------------------------------- * Response methods. *----------------------------------------------------------------------- */ /** * Sets the status code of the HTTP response. The default status * code for a response is 200 if this method is not * called. *

* An HTTP status phrase will be chosen based on the given * status code. For example, the status code 404 will get * the status phrase "Not Found". *

* If this method is called, it must be called before * sendHeaders is either directly or indirectly called. * Otherwise, it will have no effect. * * @param code * The HTTP status code, such as 200 or * 404. If < 0, the HTTP status code will * not be changed. * * @see #sendHeaders(int, String, int) */ public void setStatus(int code) { if (code >= 0) { setStatus(code, HttpUtil.getStatusPhrase(code)); } } /** * Set the HTTP status code and status phrase of this request. The given * status will be sent to the client when the user directly or indirectly * calls the method sendHeaders. The given status phrase * replaces the default HTTP status phrase normally associated with the * given status code. * * @param code * The HTTP status code, such as 200 or * 404. * * @param message * The HTTP status phrase, such as "Okey dokey" or * "I don't see it". * * @see #sendHeaders(int, String, int) */ protected void setStatus(int code, String message) { this.statusCode = code; this.statusPhrase = message; } /** * Return the status code. */ public int getStatus() { return statusCode; } /** * Return uses of this socket */ public int getReuseCount() { return server.maxRequests - requestsLeft; } /** * Adds a response header to the HTTP response. In order to do fancier * things like appending a value to an existing response header, the * user may directly access the responseHeaders field. *

* If this method is called, it must be called before * sendHeaders is either directly or indirectly called. * Otherwise, it will have no effect. * * @param key * The header name. * * @param value * The value for the request header. * * @see #sendHeaders(int, String, int) * @see #responseHeaders */ public void addHeader(String key, String value) { responseHeaders.add(key, value); } /** * Adds a response header to the HTTP response. In order to do fancier * things like appending a value to an existing response header, the * user may directly access the responseHeaders field. *

* If this method is called, it must be called before * sendHeaders is either directly or indirectly called. * Otherwise, it will have no effect. * * @param line * The HTTP response header, of the form * "key: value". * * @see #sendHeaders(int, String, int) * @see #responseHeaders */ public void addHeader(String line) { int dots = line.indexOf(':'); String key = line.substring(0, dots); String value = line.substring(dots + 1).trim(); addHeader(key, value); } /** * Sends an HTTP response to the client. *

* This method first calls sendHeaders to send the HTTP * response headers, then sends the given byte array as the HTTP * response body. If the request method is HEAD, or the result * code is "204", the body is not sent. *

* The "Content-Length" will be set to the length of the given byte array. * The "Content-Type" will be set to the given MIME type. * * @param body * The array of bytes to send as the HTTP response body. May * not be null. * * @param type * The MIME type of the response, such as "text/html". May be * null to use the existing "Content-Type" * response header (if any). * * @throws IOException * if there was an I/O error while sending the response to * the client. * * @see #sendHeaders(int, String, int) */ public void sendResponse(byte[] body, String type) throws IOException { if (statusCode == 204) { sendHeaders(-1, type, 0); } else { sendHeaders(-1, type, body.length); if (!method.equals("HEAD")) { out.write(body); } } } /** * Sends an HTTP response to the client. *

* This method first calls sendHeaders to send the HTTP * response headers. It then writes out the given string to the client * as a sequence of bytes. Each character in the string is written out * by discarding its high eight bits. *

* The "Content-Length" will be set to the length of the string. * The "Content-Type" will be set to the given MIME type. *

* Note: to use a different character encoding, use * sendResponse(body.getBytes(encoding)...) instead. * * @param body * The string to send as the HTTP response body. May * not be null. If the request method is HEAD, * the body is not sent. * * @param type * The MIME type of the response, such as "text/html". May be * null to preserve the existing "Content-Type" * response header (if any). * * @param code * The HTTP status code for the response, such as * 200. May be < 0 to preserve the existing * status code. * * @throws IOException * if there was an I/O error while sending the response to * the client. * * @see #sendHeaders(int, String, int) */ public void sendResponse(String body, String type, int code) throws IOException { if (statusCode == 204) { sendHeaders(-1, type, 0); } else { sendHeaders(code, type, body.length()); if (!"HEAD".equals(method)) { out.writeBytes(body); } } } /** * Convenience method that sends an HTTP response to the client * with a "Content-Type" of "text/html" and the default HTTP status * code. * * @param body * The string to send as the HTTP response body. * * @see #sendResponse(String, String, int) */ public void sendResponse(String body) throws IOException { sendResponse(body, "text/html", -1); } /** * Convenience method that sends an HTTP response to the client * with the default HTTP status code. * * @param body * The string to send as the HTTP response body. * If the request method is HEAD, * only the headers are sent to the client. * * @param type * The MIME type of the response. * * @see #sendResponse(String, String, int) */ public void sendResponse(String body, String type) throws IOException { sendResponse(body, type, -1); } /** * Sends the contents of the given input stream as the HTTP response. *

* This method first calls sendHeaders to send the HTTP * response headers. It then transfers a total of length * bytes of data from the given input stream to the client as the * HTTP response body. *

* This method takes care of setting the "Content-Length" header * if the actual content length is known, or the "Transfer-Encoding" * header if the content length is not known (for HTTP/1.1 clients only). *

* This method may set the keepAlive to false * before returning, if fewer than length bytes could be * read. If the request method is HEAD, only the headers are sent. * * @param in * The input stream to read from. * * @param length * The content length. The number of bytes to send to the * client. May be < 0, in which case this method will read * until reaching the end of the input stream. * * @param type * The MIME type of the response, such as "text/html". May be * null to preserve the existing "Content-Type" * response header (if any). * * @param code * The HTTP status code for the response, such as * 200. May be < 0 to preserve the existing * status code. * * @throws IOException * if there was an I/O error while sending the response to * the client. */ public void sendResponse(InputStream in, int length, String type, int code) throws IOException { HttpInputStream hin = new HttpInputStream(in); byte[] buf = new byte[server.bufsize]; if (length >= 0) { sendHeaders(code, type, length); if (!method.equals("HEAD")) { if (hin.copyTo(out, length, buf) != length) { keepAlive = false; } } } else if (version <= 10) { keepAlive = false; sendHeaders(code, type, -1); if (!method.equals("HEAD")) { hin.copyTo(out, -1, buf); } } else { if (method.equals("HEAD")) { sendHeaders(code, type, -1); return; } addHeader("Transfer-Encoding", "chunked"); sendHeaders(code, type, -1); while (true) { int count = hin.read(buf); if (count < 0) { out.writeBytes("0\r\n\r\n"); break; } out.writeBytes(Integer.toHexString(count) + "\r\n"); out.write(buf, 0, count); out.writeBytes("\r\n"); } } } /** * Sends a HTTP error response to the client. * * @param code * The HTTP status code. * * @param clientMessage * A short message to be included in the error response * and logged to the server. */ public void sendError(int code, String clientMessage) { sendError(code, clientMessage, null); } /** * Sends a HTTP error response to the client. * * @param code * The HTTP status code. * * @param clientMessage * A short message to be included in the error response. * * @param logMessage * A short message to be logged to the server. This message is * not sent to the client. */ public void sendError(int code, String clientMessage, String logMessage) { setStatus(code); server.errorCount++; String message = clientMessage; if (message == null) { message = logMessage; logMessage = null; } log(Server.LOG_LOG, "Error", statusCode + " " + statusPhrase + ": " + message); if (logMessage != null) { log(Server.LOG_LOG, logMessage); } keepAlive = false; if (headersSent) { /* * The headers have already been sent. We can't send an error * message in the middle of an existing response, so just close * this request. */ return; } String body = "\n\n" + "Error: " + statusCode + "\n" + "\nGot the error: " + statusPhrase + "
\nwhile trying to obtain " + ((url == null) ? "unknown URL" : HttpUtil.htmlEncode(url)) + "
\n" + HttpUtil.htmlEncode(clientMessage) + "\n\n"; try { sendResponse(body, "text/html", statusCode); } catch (IOException e) { /* * Don't throw an error in the process of sending an error * message! */ } } /** * Sends the HTTP status line and response headers to the client. This * method is automatically invoked by sendResponse, but * can be manually invoked if the user needs direct access to the * client's output stream. If this method is not called, then the * HTTP status and response headers will not automatically be sent to * the client; the user would be responsible for forming the entire * HTTP response. *

* The user may call the addHeader method or modify the * responseHeaders field before calling this method. * This method then adds a number of HTTP headers, as follows:

    *
  • "Date" - the current time, if this header is not already present. *
  • "Server" - the server's name (from server.name), if * this header is not already present. *
  • "Connection" - "Keep-Alive" or "close", depending upon the * keepAlive field. *
  • "Content-Length" - set to the given length. *
  • "Content-Type" - set to the given type. *
*

* The string used for "Connection" header actually comes from the * connectionHeader field. * * @param code * The HTTP status code for the response, such as * 200. May be < 0 to preserve the existing * status code. * * @param type * The MIME type of the response, such as "text/html". May be * null to preserve the existing "Content-Type" * response header (if any). * * @param length * The length of the response body. May be < 0 if the length * is unknown and/or to preserve the existing "Content-Length" * response header (if any). * * @throws IOException * if there was an I/O error while sending the headers to * the client. * * @see #setStatus(int) * @see #addHeader(String, String) * @see #sendResponse(String, String, int) * @see #connectionHeader */ public void sendHeaders(int code, String type, int length) throws IOException { setStatus(code); if ((length == 0) && (statusCode == 200)) { /* * No Content. */ setStatus(204); } responseHeaders.putIfNotPresent("Date", HttpUtil.formatTime()); if (server.name != null) { responseHeaders.putIfNotPresent("Server", server.name); } String str = shouldKeepAlive() ? "Keep-Alive" : "close"; responseHeaders.put(connectionHeader, str); if (length >= 0) { responseHeaders.put("Content-Length", Integer.toString(length)); } if (type != null) { responseHeaders.putIfNotPresent("Content-Type", type); } out.sendHeaders(this); // System.out.println("*** Sending headers: " +url+ " " + responseHeaders); headersSent = true; } /** * Send the response headers to the client. * This consists of standard plus added headers. The handler is reponsible * for sending the reponse body. * * @param type The document mime type * @param length the document length * * @see Request#addHeader(String) * @see Request#sendResponse(String) * @see Request#setStatus(int) */ /** * Responds to an HTTP request with a redirection reply, telling the * client that the requested url has moved. Generally, this is used if * the client did not put a '/' on the end of a directory. * * @param url * The URL the client should have requested. This URL may be * fully-qualified (in the form "http://....") or host-relative * (in the form "/..."). * * @param body * The body of the redirect response, or null to * send a hardcoded message. */ public void redirect(String url, String body) throws IOException { if (url.startsWith("/")) { url = serverUrl() + url; } addHeader("Location", url); if (body == null) { body = "Moved

look for " + url + "

"; } sendResponse(body, "text/html", 302); } /** * Returns the server's fully-qualified base URL. This is "http://" * followed by the server's hostname and port. *

* If the HTTP request header "Host" is present, it specifies the * hostname and port that will be used instead of the server's internal * name for itself. Due bugs in certain browsers, when using the server's * internal name, the port number will be elided if it is 80. * * @return The string representation of the server's URL. */ public String serverUrl() { String host = headers.get("Host"); if (host == null) { host = server.hostName; } int index = host.lastIndexOf(":"); if ((index < 0) && (server.listen.getLocalPort() != 80)) { host += ":" + server.listen.getLocalPort(); } if (serverProtocol != null) { return serverProtocol + "://" + host; } else { return server.protocol + "://" + host; } } /** * The HttpOutputStream provides the convenience method * writeBytes for writing the byte representation of a * string, without bringing in the overhead and the deprecated warnings * associated with a java.io.DataOutputStream. *

* The other methods in this class are here to allow the * FilterHandler and ChainSawHandler to * alter the behavior in an implementation specific way. This behavior * is unfortunate, and might go away when a better strategy comes along. */ public static class HttpOutputStream extends FilterOutputStream { /** * Count the number of bytes that are written to this stream */ public int bytesWritten = 0; public HttpOutputStream(OutputStream out) { super(out); } public void writeBytes(String s) throws IOException { int len = s.length(); for (int i = 0; i < len; i++) { this.out.write((byte) s.charAt(i)); } bytesWritten += len; } public void write(byte b) throws IOException { this.out.write(b); bytesWritten++; } public void write(byte[] buf, int off, int len) throws IOException { this.out.write(buf, off, len); bytesWritten += len; } public void sendHeaders(Request request) throws IOException { writeBytes(request.protocol + " " + request.statusCode + " " + request.statusPhrase + "\r\n"); request.responseHeaders.print(this.out); writeBytes("\r\n"); } } /** * Adds the given Dictionary to the set of properties that * are searched by request.props.getProperty(). This method * is used to optimize the case when the caller has an existing * Dictionary object that should be added to the search * chain. *

* Assume the caller is constructing a new Properties * object and wants to chain it onto the front of * request.props. The following code is appropriate: *

     * /* Push a temporary Dictionary onto request.props. */
     * PropertiesList old = request.props;
     * (new PropertiesList()).addBefore(request.props);
     * request.props = request.props.getPrior();
     * request.props.put("foo", "bar");
     * request.props.put("baz", "garply");
     *
     * /* Do something that accesses new properties. */
     *     .
     *     .
     *     .
     *
     * /* Restore old Dictionary when done. */
     * request.props.remove();
     * request.props = old;
     * 
* However, addSharedProps may be called when the caller * has an existing set of Properties and is faced with * copying its contents into request.props and/or trying * to share the existing Properties object among multiple * threads concurrently. *
     * /* Some properties created at startup. */
     * static Properties P = new Properties();
     *     .
     *     .
     *     .
     * /* Share properties at runtime. */
     * request.addSharedProps(P);
     * 
is more efficient and esthetically pleasing than: *
     * foreach key in P.keys() {
     *     request.props.put(key, P.get(key));
     * }
     * 
* The given Dictionary object is added to the * Properties.getProperty() search chain before serverProps; * it will be searched after the * request.props and before serverProps. * Multiple Dictionary objects can be added and they will * be searched in the order given. The same Dictionary * object can be added multiple times safely. However, the search * chain for the given Dictionary must not refer back to * request.props itself or a circular chain will be * created causing an infinite loop: *
     * request.addSharedProps(request.props);	            // Bad
     * request.addSharedProps(request.props.getWrapped());  // Good
     * Properties d1 = new Properties(request.props);
     * request.addSharedProps(d1);                          // Bad
     * Hashtable d2 = new Hashtable();
     * Properties d3 = new Properties();
     * request.addSharedProps(d2);		            // Good
     * request.addSharedProps(d3);		            // Good
     * 
* Subsequent calls to request.props.getProperty() may * fetch properties from an added Dictionary, but * request.put() will not modify those dictionaries. * * @param d * A Dictionary of String key/value * pairs that will be added to the chain searched * when request.props.getProperty() is called. The * dictionary d is "live", meaning that external * changes to the contents of d will be seen on * subsequent calls to request.props.getProperty(). * * @return false if the dictionary had already been added * by a previous call to this method, true * otherwise. */ public boolean addSharedProps(Dictionary d) { boolean debug = server.props.get("debugProps") != null; if (props.wraps(d) != null) { if (debug) { System.out.println("addSharedProps didn't add dict" + Integer.toHexString(System.identityHashCode(d))); } return false; } PropertiesList pl = new PropertiesList(d); pl.addAfter(props); if (debug) { props.dump(true, "at addSharedProps"); } return true; } /** * Removes a Dictionary added by * addSharedProps. Dictionary objects may * be removed in any order. Dictionary objects do not need * to be removed; they will automatically get cleaned up at the end of * the request. * * @param d * The Dictionary object to remove from the * request.props.getProperty() search chain. * * @return true if the Dictionary was found * and removed, false if the Dictionary * was not found (it had already been removed or had never been * added). */ public boolean removeSharedProps(Dictionary d) { PropertiesList pl = props.wraps(d); if (pl != null && pl != props && pl != serverProps) { pl.remove(); if (server.props.get("debugProps") != null) { pl.dump(true, "at removeSharedProps"); } return true; } return false; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy