0001: /* * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/connector/http/HttpProcessor.java,v 1.46 2002/04/04 17:50:34 remm Exp $
0002: * $Revision: 1.46 $
0003: * $Date: 2002/04/04 17:50:34 $
0004: *
0005: * ====================================================================
0006: *
0007: * The Apache Software License, Version 1.1
0008: *
0009: * Copyright (c) 1999 The Apache Software Foundation. All rights
0010: * reserved.
0011: *
0012: * Redistribution and use in source and binary forms, with or without
0013: * modification, are permitted provided that the following conditions
0014: * are met:
0015: *
0016: * 1. Redistributions of source code must retain the above copyright
0017: * notice, this list of conditions and the following disclaimer.
0018: *
0019: * 2. Redistributions in binary form must reproduce the above copyright
0020: * notice, this list of conditions and the following disclaimer in
0021: * the documentation and/or other materials provided with the
0022: * distribution.
0023: *
0024: * 3. The end-user documentation included with the redistribution, if
0025: * any, must include the following acknowlegement:
0026: * "This product includes software developed by the
0027: * Apache Software Foundation (http://www.apache.org/)."
0028: * Alternately, this acknowlegement may appear in the software itself,
0029: * if and wherever such third-party acknowlegements normally appear.
0030: *
0031: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
0032: * Foundation" must not be used to endorse or promote products derived
0033: * from this software without prior written permission. For written
0034: * permission, please contact apache@apache.org.
0035: *
0036: * 5. Products derived from this software may not be called "Apache"
0037: * nor may "Apache" appear in their names without prior written
0038: * permission of the Apache Group.
0039: *
0040: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
0041: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
0042: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
0043: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
0044: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
0045: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
0046: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
0047: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
0048: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
0049: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
0050: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
0051: * SUCH DAMAGE.
0052: * ====================================================================
0053: *
0054: * This software consists of voluntary contributions made by many
0055: * individuals on behalf of the Apache Software Foundation. For more
0056: * information on the Apache Software Foundation, please see
0057: * <http://www.apache.org/>.
0058: *
0059: * [Additional notices, if required by prior licensing conditions]
0060: *
0061: */
0062:
0063: package org.apache.catalina.connector.http;
0064:
0065: import java.io.BufferedInputStream;
0066: import java.io.EOFException;
0067: import java.io.InterruptedIOException;
0068: import java.io.InputStream;
0069: import java.io.IOException;
0070: import java.io.OutputStream;
0071: import java.net.InetAddress;
0072: import java.net.Socket;
0073: import java.util.ArrayList;
0074: import java.util.Iterator;
0075: import java.util.Locale;
0076: import java.util.StringTokenizer;
0077: import java.util.TreeMap;
0078: import javax.servlet.ServletException;
0079: import javax.servlet.http.Cookie;
0080: import javax.servlet.http.HttpServletRequest;
0081: import javax.servlet.http.HttpServletResponse;
0082: import org.apache.catalina.Connector;
0083: import org.apache.catalina.Container;
0084: import org.apache.catalina.Globals;
0085: import org.apache.catalina.HttpRequest;
0086: import org.apache.catalina.HttpResponse;
0087: import org.apache.catalina.Lifecycle;
0088: import org.apache.catalina.LifecycleEvent;
0089: import org.apache.catalina.LifecycleException;
0090: import org.apache.catalina.LifecycleListener;
0091: import org.apache.catalina.Logger;
0092: import org.apache.catalina.util.FastHttpDateFormat;
0093: import org.apache.catalina.util.LifecycleSupport;
0094: import org.apache.catalina.util.RequestUtil;
0095: import org.apache.catalina.util.ServerInfo;
0096: import org.apache.catalina.util.StringManager;
0097: import org.apache.catalina.util.StringParser;
0098:
0099: /**
0100: * Implementation of a request processor (and its associated thread) that may
0101: * be used by an HttpConnector to process individual requests. The connector
0102: * will allocate a processor from its pool, assign a particular socket to it,
0103: * and the processor will then execute the processing required to complete
0104: * the request. When the processor is completed, it will recycle itself.
0105: *
0106: * @author Craig R. McClanahan
0107: * @author Remy Maucherat
0108: * @version $Revision: 1.46 $ $Date: 2002/04/04 17:50:34 $
0109: * @deprecated
0110: */
0111:
0112: final class HttpProcessor implements Lifecycle, Runnable {
0113:
0114: // ----------------------------------------------------- Manifest Constants
0115:
0116: /**
0117: * Server information string for this server.
0118: */
0119: private static final String SERVER_INFO = ServerInfo
0120: .getServerInfo()
0121: + " (HTTP/1.1 Connector)";
0122:
0123: // ----------------------------------------------------------- Constructors
0124:
0125: /**
0126: * Construct a new HttpProcessor associated with the specified connector.
0127: *
0128: * @param connector HttpConnector that owns this processor
0129: * @param id Identifier of this HttpProcessor (unique per connector)
0130: */
0131: public HttpProcessor(HttpConnector connector, int id) {
0132:
0133: super ();
0134: this .connector = connector;
0135: this .debug = connector.getDebug();
0136: this .id = id;
0137: this .proxyName = connector.getProxyName();
0138: this .proxyPort = connector.getProxyPort();
0139: this .request = (HttpRequestImpl) connector.createRequest();
0140: this .response = (HttpResponseImpl) connector.createResponse();
0141: this .serverPort = connector.getPort();
0142: this .threadName = "HttpProcessor[" + connector.getPort() + "]["
0143: + id + "]";
0144:
0145: }
0146:
0147: // ----------------------------------------------------- Instance Variables
0148:
0149: /**
0150: * Is there a new socket available?
0151: */
0152: private boolean available = false;
0153:
0154: /**
0155: * The HttpConnector with which this processor is associated.
0156: */
0157: private HttpConnector connector = null;
0158:
0159: /**
0160: * The debugging detail level for this component.
0161: */
0162: private int debug = 0;
0163:
0164: /**
0165: * The identifier of this processor, unique per connector.
0166: */
0167: private int id = 0;
0168:
0169: /**
0170: * The lifecycle event support for this component.
0171: */
0172: private LifecycleSupport lifecycle = new LifecycleSupport(this );
0173:
0174: /**
0175: * The match string for identifying a session ID parameter.
0176: */
0177: private static final String match = ";"
0178: + Globals.SESSION_PARAMETER_NAME + "=";
0179:
0180: /**
0181: * The match string for identifying a session ID parameter.
0182: */
0183: private static final char[] SESSION_ID = match.toCharArray();
0184:
0185: /**
0186: * The string parser we will use for parsing request lines.
0187: */
0188: private StringParser parser = new StringParser();
0189:
0190: /**
0191: * The proxy server name for our Connector.
0192: */
0193: private String proxyName = null;
0194:
0195: /**
0196: * The proxy server port for our Connector.
0197: */
0198: private int proxyPort = 0;
0199:
0200: /**
0201: * The HTTP request object we will pass to our associated container.
0202: */
0203: private HttpRequestImpl request = null;
0204:
0205: /**
0206: * The HTTP response object we will pass to our associated container.
0207: */
0208: private HttpResponseImpl response = null;
0209:
0210: /**
0211: * The actual server port for our Connector.
0212: */
0213: private int serverPort = 0;
0214:
0215: /**
0216: * The string manager for this package.
0217: */
0218: protected StringManager sm = StringManager
0219: .getManager(Constants.Package);
0220:
0221: /**
0222: * The socket we are currently processing a request for. This object
0223: * is used for inter-thread communication only.
0224: */
0225: private Socket socket = null;
0226:
0227: /**
0228: * Has this component been started yet?
0229: */
0230: private boolean started = false;
0231:
0232: /**
0233: * The shutdown signal to our background thread
0234: */
0235: private boolean stopped = false;
0236:
0237: /**
0238: * The background thread.
0239: */
0240: private Thread thread = null;
0241:
0242: /**
0243: * The name to register for the background thread.
0244: */
0245: private String threadName = null;
0246:
0247: /**
0248: * The thread synchronization object.
0249: */
0250: private Object threadSync = new Object();
0251:
0252: /**
0253: * Keep alive indicator.
0254: */
0255: private boolean keepAlive = false;
0256:
0257: /**
0258: * HTTP/1.1 client.
0259: */
0260: private boolean http11 = true;
0261:
0262: /**
0263: * True if the client has asked to recieve a request acknoledgement. If so
0264: * the server will send a preliminary 100 Continue response just after it
0265: * has successfully parsed the request headers, and before starting
0266: * reading the request entity body.
0267: */
0268: private boolean sendAck = false;
0269:
0270: /**
0271: * Ack string when pipelining HTTP requests.
0272: */
0273: private static final byte[] ack = (new String(
0274: "HTTP/1.1 100 Continue\r\n\r\n")).getBytes();
0275:
0276: /**
0277: * CRLF.
0278: */
0279: private static final byte[] CRLF = (new String("\r\n")).getBytes();
0280:
0281: /**
0282: * Line buffer.
0283: */
0284: //private char[] lineBuffer = new char[4096];
0285:
0286: /**
0287: * Request line buffer.
0288: */
0289: private HttpRequestLine requestLine = new HttpRequestLine();
0290:
0291: /**
0292: * Processor state
0293: */
0294: private int status = Constants.PROCESSOR_IDLE;
0295:
0296: // --------------------------------------------------------- Public Methods
0297:
0298: /**
0299: * Return a String value representing this object.
0300: */
0301: public String toString() {
0302:
0303: return (this .threadName);
0304:
0305: }
0306:
0307: // -------------------------------------------------------- Package Methods
0308:
0309: /**
0310: * Process an incoming TCP/IP connection on the specified socket. Any
0311: * exception that occurs during processing must be logged and swallowed.
0312: * <b>NOTE</b>: This method is called from our Connector's thread. We
0313: * must assign it to our own thread so that multiple simultaneous
0314: * requests can be handled.
0315: *
0316: * @param socket TCP socket to process
0317: */
0318: synchronized void assign(Socket socket) {
0319:
0320: // Wait for the Processor to get the previous Socket
0321: while (available) {
0322: try {
0323: wait();
0324: } catch (InterruptedException e) {
0325: }
0326: }
0327:
0328: // Store the newly available Socket and notify our thread
0329: this .socket = socket;
0330: available = true;
0331: notifyAll();
0332:
0333: if ((debug >= 1) && (socket != null))
0334: log(" An incoming request is being assigned");
0335:
0336: }
0337:
0338: // -------------------------------------------------------- Private Methods
0339:
0340: /**
0341: * Await a newly assigned Socket from our Connector, or <code>null</code>
0342: * if we are supposed to shut down.
0343: */
0344: private synchronized Socket await() {
0345:
0346: // Wait for the Connector to provide a new Socket
0347: while (!available) {
0348: try {
0349: wait();
0350: } catch (InterruptedException e) {
0351: }
0352: }
0353:
0354: // Notify the Connector that we have received this Socket
0355: Socket socket = this .socket;
0356: available = false;
0357: notifyAll();
0358:
0359: if ((debug >= 1) && (socket != null))
0360: log(" The incoming request has been awaited");
0361:
0362: return (socket);
0363:
0364: }
0365:
0366: /**
0367: * Log a message on the Logger associated with our Container (if any)
0368: *
0369: * @param message Message to be logged
0370: */
0371: private void log(String message) {
0372:
0373: Logger logger = connector.getContainer().getLogger();
0374: if (logger != null)
0375: logger.log(threadName + " " + message);
0376:
0377: }
0378:
0379: /**
0380: * Log a message on the Logger associated with our Container (if any)
0381: *
0382: * @param message Message to be logged
0383: * @param throwable Associated exception
0384: */
0385: private void log(String message, Throwable throwable) {
0386:
0387: Logger logger = connector.getContainer().getLogger();
0388: if (logger != null)
0389: logger.log(threadName + " " + message, throwable);
0390:
0391: }
0392:
0393: /**
0394: * Parse the value of an <code>Accept-Language</code> header, and add
0395: * the corresponding Locales to the current request.
0396: *
0397: * @param value The value of the <code>Accept-Language</code> header.
0398: */
0399: private void parseAcceptLanguage(String value) {
0400:
0401: // Store the accumulated languages that have been requested in
0402: // a local collection, sorted by the quality value (so we can
0403: // add Locales in descending order). The values will be ArrayLists
0404: // containing the corresponding Locales to be added
0405: TreeMap locales = new TreeMap();
0406:
0407: // Preprocess the value to remove all whitespace
0408: int white = value.indexOf(' ');
0409: if (white < 0)
0410: white = value.indexOf('\t');
0411: if (white >= 0) {
0412: StringBuffer sb = new StringBuffer();
0413: int len = value.length();
0414: for (int i = 0; i < len; i++) {
0415: char ch = value.charAt(i);
0416: if ((ch != ' ') && (ch != '\t'))
0417: sb.append(ch);
0418: }
0419: value = sb.toString();
0420: }
0421:
0422: // Process each comma-delimited language specification
0423: parser.setString(value); // ASSERT: parser is available to us
0424: int length = parser.getLength();
0425: while (true) {
0426:
0427: // Extract the next comma-delimited entry
0428: int start = parser.getIndex();
0429: if (start >= length)
0430: break;
0431: int end = parser.findChar(',');
0432: String entry = parser.extract(start, end).trim();
0433: parser.advance(); // For the following entry
0434:
0435: // Extract the quality factor for this entry
0436: double quality = 1.0;
0437: int semi = entry.indexOf(";q=");
0438: if (semi >= 0) {
0439: try {
0440: quality = Double.parseDouble(entry
0441: .substring(semi + 3));
0442: } catch (NumberFormatException e) {
0443: quality = 0.0;
0444: }
0445: entry = entry.substring(0, semi);
0446: }
0447:
0448: // Skip entries we are not going to keep track of
0449: if (quality < 0.00005)
0450: continue; // Zero (or effectively zero) quality factors
0451: if ("*".equals(entry))
0452: continue; // FIXME - "*" entries are not handled
0453:
0454: // Extract the language and country for this entry
0455: String language = null;
0456: String country = null;
0457: String variant = null;
0458: int dash = entry.indexOf('-');
0459: if (dash < 0) {
0460: language = entry;
0461: country = "";
0462: variant = "";
0463: } else {
0464: language = entry.substring(0, dash);
0465: country = entry.substring(dash + 1);
0466: int vDash = country.indexOf('-');
0467: if (vDash > 0) {
0468: String cTemp = country.substring(0, vDash);
0469: variant = country.substring(vDash + 1);
0470: country = cTemp;
0471: } else {
0472: variant = "";
0473: }
0474: }
0475:
0476: // Add a new Locale to the list of Locales for this quality level
0477: Locale locale = new Locale(language, country, variant);
0478: Double key = new Double(-quality); // Reverse the order
0479: ArrayList values = (ArrayList) locales.get(key);
0480: if (values == null) {
0481: values = new ArrayList();
0482: locales.put(key, values);
0483: }
0484: values.add(locale);
0485:
0486: }
0487:
0488: // Process the quality values in highest->lowest order (due to
0489: // negating the Double value when creating the key)
0490: Iterator keys = locales.keySet().iterator();
0491: while (keys.hasNext()) {
0492: Double key = (Double) keys.next();
0493: ArrayList list = (ArrayList) locales.get(key);
0494: Iterator values = list.iterator();
0495: while (values.hasNext()) {
0496: Locale locale = (Locale) values.next();
0497: if (debug >= 1)
0498: log(" Adding locale '" + locale + "'");
0499: request.addLocale(locale);
0500: }
0501: }
0502:
0503: }
0504:
0505: /**
0506: * Parse and record the connection parameters related to this request.
0507: *
0508: * @param socket The socket on which we are connected
0509: *
0510: * @exception IOException if an input/output error occurs
0511: * @exception ServletException if a parsing error occurs
0512: */
0513: private void parseConnection(Socket socket) throws IOException,
0514: ServletException {
0515:
0516: if (debug >= 2)
0517: log(" parseConnection: address=" + socket.getInetAddress()
0518: + ", port=" + connector.getPort());
0519: ((HttpRequestImpl) request).setInet(socket.getInetAddress());
0520: if (proxyPort != 0)
0521: request.setServerPort(proxyPort);
0522: else
0523: request.setServerPort(serverPort);
0524: request.setSocket(socket);
0525:
0526: }
0527:
0528: /**
0529: * Parse the incoming HTTP request headers, and set the appropriate
0530: * request headers.
0531: *
0532: * @param input The input stream connected to our socket
0533: *
0534: * @exception IOException if an input/output error occurs
0535: * @exception ServletException if a parsing error occurs
0536: */
0537: private void parseHeaders(SocketInputStream input)
0538: throws IOException, ServletException {
0539:
0540: while (true) {
0541:
0542: HttpHeader header = request.allocateHeader();
0543:
0544: // Read the next header
0545: input.readHeader(header);
0546: if (header.nameEnd == 0) {
0547: if (header.valueEnd == 0) {
0548: return;
0549: } else {
0550: throw new ServletException(
0551: sm
0552: .getString("httpProcessor.parseHeaders.colon"));
0553: }
0554: }
0555:
0556: String value = new String(header.value, 0, header.valueEnd);
0557: if (debug >= 1)
0558: log(" Header "
0559: + new String(header.name, 0, header.nameEnd)
0560: + " = " + value);
0561:
0562: // Set the corresponding request headers
0563: if (header.equals(DefaultHeaders.AUTHORIZATION_NAME)) {
0564: request.setAuthorization(value);
0565: } else if (header
0566: .equals(DefaultHeaders.ACCEPT_LANGUAGE_NAME)) {
0567: parseAcceptLanguage(value);
0568: } else if (header.equals(DefaultHeaders.COOKIE_NAME)) {
0569: Cookie cookies[] = RequestUtil.parseCookieHeader(value);
0570: for (int i = 0; i < cookies.length; i++) {
0571: if (cookies[i].getName().equals(
0572: Globals.SESSION_COOKIE_NAME)) {
0573: // Override anything requested in the URL
0574: if (!request.isRequestedSessionIdFromCookie()) {
0575: // Accept only the first session id cookie
0576: request.setRequestedSessionId(cookies[i]
0577: .getValue());
0578: request.setRequestedSessionCookie(true);
0579: request.setRequestedSessionURL(false);
0580: if (debug >= 1)
0581: log(" Requested cookie session id is "
0582: + ((HttpServletRequest) request
0583: .getRequest())
0584: .getRequestedSessionId());
0585: }
0586: }
0587: if (debug >= 1)
0588: log(" Adding cookie " + cookies[i].getName()
0589: + "=" + cookies[i].getValue());
0590: request.addCookie(cookies[i]);
0591: }
0592: } else if (header
0593: .equals(DefaultHeaders.CONTENT_LENGTH_NAME)) {
0594: int n = -1;
0595: try {
0596: n = Integer.parseInt(value);
0597: } catch (Exception e) {
0598: throw new ServletException(
0599: sm
0600: .getString("httpProcessor.parseHeaders.contentLength"));
0601: }
0602: request.setContentLength(n);
0603: } else if (header.equals(DefaultHeaders.CONTENT_TYPE_NAME)) {
0604: request.setContentType(value);
0605: } else if (header.equals(DefaultHeaders.HOST_NAME)) {
0606: int n = value.indexOf(':');
0607: if (n < 0) {
0608: if (connector.getScheme().equals("http")) {
0609: request.setServerPort(80);
0610: } else if (connector.getScheme().equals("https")) {
0611: request.setServerPort(443);
0612: }
0613: if (proxyName != null)
0614: request.setServerName(proxyName);
0615: else
0616: request.setServerName(value);
0617: } else {
0618: if (proxyName != null)
0619: request.setServerName(proxyName);
0620: else
0621: request.setServerName(value.substring(0, n)
0622: .trim());
0623: if (proxyPort != 0)
0624: request.setServerPort(proxyPort);
0625: else {
0626: int port = 80;
0627: try {
0628: port = Integer.parseInt(value.substring(
0629: n + 1).trim());
0630: } catch (Exception e) {
0631: throw new ServletException(
0632: sm
0633: .getString("httpProcessor.parseHeaders.portNumber"));
0634: }
0635: request.setServerPort(port);
0636: }
0637: }
0638: } else if (header.equals(DefaultHeaders.CONNECTION_NAME)) {
0639: if (header
0640: .valueEquals(DefaultHeaders.CONNECTION_CLOSE_VALUE)) {
0641: keepAlive = false;
0642: response.setHeader("Connection", "close");
0643: }
0644: //request.setConnection(header);
0645: /*
0646: if ("keep-alive".equalsIgnoreCase(value)) {
0647: keepAlive = true;
0648: }
0649: */
0650: } else if (header.equals(DefaultHeaders.EXPECT_NAME)) {
0651: if (header.valueEquals(DefaultHeaders.EXPECT_100_VALUE))
0652: sendAck = true;
0653: else
0654: throw new ServletException(
0655: sm
0656: .getString("httpProcessor.parseHeaders.unknownExpectation"));
0657: } else if (header
0658: .equals(DefaultHeaders.TRANSFER_ENCODING_NAME)) {
0659: //request.setTransferEncoding(header);
0660: }
0661:
0662: request.nextHeader();
0663:
0664: }
0665:
0666: }
0667:
0668: /**
0669: * Parse the incoming HTTP request and set the corresponding HTTP request
0670: * properties.
0671: *
0672: * @param input The input stream attached to our socket
0673: * @param output The output stream of the socket
0674: *
0675: * @exception IOException if an input/output error occurs
0676: * @exception ServletException if a parsing error occurs
0677: */
0678: private void parseRequest(SocketInputStream input,
0679: OutputStream output) throws IOException, ServletException {
0680:
0681: // Parse the incoming request line
0682: input.readRequestLine(requestLine);
0683:
0684: // When the previous method returns, we're actually processing a
0685: // request
0686: status = Constants.PROCESSOR_ACTIVE;
0687:
0688: String method = new String(requestLine.method, 0,
0689: requestLine.methodEnd);
0690: String uri = null;
0691: String protocol = new String(requestLine.protocol, 0,
0692: requestLine.protocolEnd);
0693:
0694: //System.out.println(" Method:" + method + "_ Uri:" + uri
0695: // + "_ Protocol:" + protocol);
0696:
0697: if (protocol.length() == 0)
0698: protocol = "HTTP/0.9";
0699:
0700: // Now check if the connection should be kept alive after parsing the
0701: // request.
0702: if (protocol.equals("HTTP/1.1")) {
0703: http11 = true;
0704: sendAck = false;
0705: } else {
0706: http11 = false;
0707: sendAck = false;
0708: // For HTTP/1.0, connection are not persistent by default,
0709: // unless specified with a Connection: Keep-Alive header.
0710: keepAlive = false;
0711: }
0712:
0713: // Validate the incoming request line
0714: if (method.length() < 1) {
0715: throw new ServletException(sm
0716: .getString("httpProcessor.parseRequest.method"));
0717: } else if (requestLine.uriEnd < 1) {
0718: throw new ServletException(sm
0719: .getString("httpProcessor.parseRequest.uri"));
0720: }
0721:
0722: // Parse any query parameters out of the request URI
0723: int question = requestLine.indexOf("?");
0724: if (question >= 0) {
0725: request.setQueryString(new String(requestLine.uri,
0726: question + 1, requestLine.uriEnd - question - 1));
0727: if (debug >= 1)
0728: log(" Query string is "
0729: + ((HttpServletRequest) request.getRequest())
0730: .getQueryString());
0731: uri = new String(requestLine.uri, 0, question);
0732: } else {
0733: request.setQueryString(null);
0734: uri = new String(requestLine.uri, 0, requestLine.uriEnd);
0735: }
0736:
0737: // Checking for an absolute URI (with the HTTP protocol)
0738: if (!uri.startsWith("/")) {
0739: int pos = uri.indexOf("://");
0740: // Parsing out protocol and host name
0741: if (pos != -1) {
0742: pos = uri.indexOf('/', pos + 3);
0743: if (pos == -1) {
0744: uri = "";
0745: } else {
0746: uri = uri.substring(pos);
0747: }
0748: }
0749: }
0750:
0751: // Parse any requested session ID out of the request URI
0752: int semicolon = uri.indexOf(match);
0753: if (semicolon >= 0) {
0754: String rest = uri.substring(semicolon + match.length());
0755: int semicolon2 = rest.indexOf(';');
0756: if (semicolon2 >= 0) {
0757: request.setRequestedSessionId(rest.substring(0,
0758: semicolon2));
0759: rest = rest.substring(semicolon2);
0760: } else {
0761: request.setRequestedSessionId(rest);
0762: rest = "";
0763: }
0764: request.setRequestedSessionURL(true);
0765: uri = uri.substring(0, semicolon) + rest;
0766: if (debug >= 1)
0767: log(" Requested URL session id is "
0768: + ((HttpServletRequest) request.getRequest())
0769: .getRequestedSessionId());
0770: } else {
0771: request.setRequestedSessionId(null);
0772: request.setRequestedSessionURL(false);
0773: }
0774:
0775: // Normalize URI (using String operations at the moment)
0776: String normalizedUri = normalize(uri);
0777: if (debug >= 1)
0778: log("Normalized: '" + uri + "' to '" + normalizedUri + "'");
0779:
0780: // Set the corresponding request properties
0781: ((HttpRequest) request).setMethod(method);
0782: request.setProtocol(protocol);
0783: if (normalizedUri != null) {
0784: ((HttpRequest) request).setRequestURI(normalizedUri);
0785: } else {
0786: ((HttpRequest) request).setRequestURI(uri);
0787: }
0788: request.setSecure(connector.getSecure());
0789: request.setScheme(connector.getScheme());
0790:
0791: if (normalizedUri == null) {
0792: log(" Invalid request URI: '" + uri + "'");
0793: throw new ServletException("Invalid URI: " + uri + "'");
0794: }
0795:
0796: if (debug >= 1)
0797: log(" Request is '" + method + "' for '" + uri
0798: + "' with protocol '" + protocol + "'");
0799:
0800: }
0801:
0802: /**
0803: * Return a context-relative path, beginning with a "/", that represents
0804: * the canonical version of the specified path after ".." and "." elements
0805: * are resolved out. If the specified path attempts to go outside the
0806: * boundaries of the current context (i.e. too many ".." path elements
0807: * are present), return <code>null</code> instead.
0808: *
0809: * @param path Path to be normalized
0810: */
0811: protected String normalize(String path) {
0812:
0813: if (path == null)
0814: return null;
0815:
0816: // Create a place for the normalized path
0817: String normalized = path;
0818:
0819: // Normalize "/%7E" and "/%7e" at the beginning to "/~"
0820: if (normalized.startsWith("/%7E")
0821: || normalized.startsWith("/%7e"))
0822: normalized = "/~" + normalized.substring(4);
0823:
0824: // Prevent encoding '%', '/', '.' and '\', which are special reserved
0825: // characters
0826: if ((normalized.indexOf("%25") >= 0)
0827: || (normalized.indexOf("%2F") >= 0)
0828: || (normalized.indexOf("%2E") >= 0)
0829: || (normalized.indexOf("%5C") >= 0)
0830: || (normalized.indexOf("%2f") >= 0)
0831: || (normalized.indexOf("%2e") >= 0)
0832: || (normalized.indexOf("%5c") >= 0)) {
0833: return null;
0834: }
0835:
0836: if (normalized.equals("/."))
0837: return "/";
0838:
0839: // Normalize the slashes and add leading slash if necessary
0840: if (normalized.indexOf('\\') >= 0)
0841: normalized = normalized.replace('\\', '/');
0842: if (!normalized.startsWith("/"))
0843: normalized = "/" + normalized;
0844:
0845: // Resolve occurrences of "//" in the normalized path
0846: while (true) {
0847: int index = normalized.indexOf("//");
0848: if (index < 0)
0849: break;
0850: normalized = normalized.substring(0, index)
0851: + normalized.substring(index + 1);
0852: }
0853:
0854: // Resolve occurrences of "/./" in the normalized path
0855: while (true) {
0856: int index = normalized.indexOf("/./");
0857: if (index < 0)
0858: break;
0859: normalized = normalized.substring(0, index)
0860: + normalized.substring(index + 2);
0861: }
0862:
0863: // Resolve occurrences of "/../" in the normalized path
0864: while (true) {
0865: int index = normalized.indexOf("/../");
0866: if (index < 0)
0867: break;
0868: if (index == 0)
0869: return (null); // Trying to go outside our context
0870: int index2 = normalized.lastIndexOf('/', index - 1);
0871: normalized = normalized.substring(0, index2)
0872: + normalized.substring(index + 3);
0873: }
0874:
0875: // Declare occurrences of "/..." (three or more dots) to be invalid
0876: // (on some Windows platforms this walks the directory tree!!!)
0877: if (normalized.indexOf("/...") >= 0)
0878: return (null);
0879:
0880: // Return the normalized path that we have completed
0881: return (normalized);
0882:
0883: }
0884:
0885: /**
0886: * Send a confirmation that a request has been processed when pipelining.
0887: * HTTP/1.1 100 Continue is sent back to the client.
0888: *
0889: * @param output Socket output stream
0890: */
0891: private void ackRequest(OutputStream output) throws IOException {
0892: if (sendAck)
0893: output.write(ack);
0894: }
0895:
0896: /**
0897: * Process an incoming HTTP request on the Socket that has been assigned
0898: * to this Processor. Any exceptions that occur during processing must be
0899: * swallowed and dealt with.
0900: *
0901: * @param socket The socket on which we are connected to the client
0902: */
0903: private void process(Socket socket) {
0904:
0905: boolean ok = true;
0906: boolean finishResponse = true;
0907: SocketInputStream input = null;
0908: OutputStream output = null;
0909:
0910: // Construct and initialize the objects we will need
0911: try {
0912: input = new SocketInputStream(socket.getInputStream(),
0913: connector.getBufferSize());
0914: } catch (Exception e) {
0915: log("process.create", e);
0916: ok = false;
0917: }
0918:
0919: keepAlive = true;
0920:
0921: while (!stopped && ok && keepAlive) {
0922:
0923: finishResponse = true;
0924:
0925: try {
0926: request.setStream(input);
0927: request.setResponse(response);
0928: output = socket.getOutputStream();
0929: response.setStream(output);
0930: response.setRequest(request);
0931: ((HttpServletResponse) response.getResponse())
0932: .setHeader("Server", SERVER_INFO);
0933: } catch (Exception e) {
0934: log("process.create", e);
0935: ok = false;
0936: }
0937:
0938: // Parse the incoming request
0939: try {
0940: if (ok) {
0941: parseConnection(socket);
0942: parseRequest(input, output);
0943: if (!request.getRequest().getProtocol().startsWith(
0944: "HTTP/0"))
0945: parseHeaders(input);
0946: if (http11) {
0947: // Sending a request acknowledge back to the client if
0948: // requested.
0949: ackRequest(output);
0950: // If the protocol is HTTP/1.1, chunking is allowed.
0951: if (connector.isChunkingAllowed())
0952: response.setAllowChunking(true);
0953: }
0954: }
0955: } catch (EOFException e) {
0956: // It's very likely to be a socket disconnect on either the
0957: // client or the server
0958: ok = false;
0959: finishResponse = false;
0960: } catch (ServletException e) {
0961: ok = false;
0962: try {
0963: ((HttpServletResponse) response.getResponse())
0964: .sendError(HttpServletResponse.SC_BAD_REQUEST);
0965: } catch (Exception f) {
0966: ;
0967: }
0968: } catch (InterruptedIOException e) {
0969: if (debug > 1) {
0970: try {
0971: log("process.parse", e);
0972: ((HttpServletResponse) response.getResponse())
0973: .sendError(HttpServletResponse.SC_BAD_REQUEST);
0974: } catch (Exception f) {
0975: ;
0976: }
0977: }
0978: ok = false;
0979: } catch (Exception e) {
0980: try {
0981: log("process.parse", e);
0982: ((HttpServletResponse) response.getResponse())
0983: .sendError(HttpServletResponse.SC_BAD_REQUEST);
0984: } catch (Exception f) {
0985: ;
0986: }
0987: ok = false;
0988: }
0989:
0990: // Ask our Container to process this request
0991: try {
0992: ((HttpServletResponse) response).setHeader("Date",
0993: FastHttpDateFormat.getCurrentDate());
0994: if (ok) {
0995: connector.getContainer().invoke(request, response);
0996: }
0997: } catch (ServletException e) {
0998: log("process.invoke", e);
0999: try {
1000: ((HttpServletResponse) response.getResponse())
1001: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1002: } catch (Exception f) {
1003: ;
1004: }
1005: ok = false;
1006: } catch (InterruptedIOException e) {
1007: ok = false;
1008: } catch (Throwable e) {
1009: log("process.invoke", e);
1010: try {
1011: ((HttpServletResponse) response.getResponse())
1012: .sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
1013: } catch (Exception f) {
1014: ;
1015: }
1016: ok = false;
1017: }
1018:
1019: // Finish up the handling of the request
1020: if (finishResponse) {
1021: try {
1022: response.finishResponse();
1023: } catch (IOException e) {
1024: ok = false;
1025: } catch (Throwable e) {
1026: log("process.invoke", e);
1027: ok = false;
1028: }
1029: try {
1030: request.finishRequest();
1031: } catch (IOException e) {
1032: ok = false;
1033: } catch (Throwable e) {
1034: log("process.invoke", e);
1035: ok = false;
1036: }
1037: try {
1038: if (output != null)
1039: output.flush();
1040: } catch (IOException e) {
1041: ok = false;
1042: }
1043: }
1044:
1045: // We have to check if the connection closure has been requested
1046: // by the application or the response stream (in case of HTTP/1.0
1047: // and keep-alive).
1048: if ("close".equals(response.getHeader("Connection"))) {
1049: keepAlive = false;
1050: }
1051:
1052: // End of request processing
1053: status = Constants.PROCESSOR_IDLE;
1054:
1055: // Recycling the request and the response objects
1056: request.recycle();
1057: response.recycle();
1058:
1059: }
1060:
1061: try {
1062: shutdownInput(input);
1063: socket.close();
1064: } catch (IOException e) {
1065: ;
1066: } catch (Throwable e) {
1067: log("process.invoke", e);
1068: }
1069: socket = null;
1070:
1071: }
1072:
1073: protected void shutdownInput(InputStream input) {
1074: try {
1075: int available = input.available();
1076: // skip any unread (bogus) bytes
1077: if (available > 0) {
1078: input.skip(available);
1079: }
1080: } catch (Throwable e) {
1081: ;
1082: }
1083: }
1084:
1085: // ---------------------------------------------- Background Thread Methods
1086:
1087: /**
1088: * The background thread that listens for incoming TCP/IP connections and
1089: * hands them off to an appropriate processor.
1090: */
1091: public void run() {
1092:
1093: // Process requests until we receive a shutdown signal
1094: while (!stopped) {
1095:
1096: // Wait for the next socket to be assigned
1097: Socket socket = await();
1098: if (socket == null)
1099: continue;
1100:
1101: // Process the request from this socket
1102: try {
1103: process(socket);
1104: } catch (Throwable t) {
1105: log("process.invoke", t);
1106: }
1107:
1108: // Finish up this request
1109: connector.recycle(this );
1110:
1111: }
1112:
1113: // Tell threadStop() we have shut ourselves down successfully
1114: synchronized (threadSync) {
1115: threadSync.notifyAll();
1116: }
1117:
1118: }
1119:
1120: /**
1121: * Start the background processing thread.
1122: */
1123: private void threadStart() {
1124:
1125: log(sm.getString("httpProcessor.starting"));
1126:
1127: thread = new Thread(this , threadName);
1128: thread.setDaemon(true);
1129: thread.start();
1130:
1131: if (debug >= 1)
1132: log(" Background thread has been started");
1133:
1134: }
1135:
1136: /**
1137: * Stop the background processing thread.
1138: */
1139: private void threadStop() {
1140:
1141: log(sm.getString("httpProcessor.stopping"));
1142:
1143: stopped = true;
1144: assign(null);
1145:
1146: if (status != Constants.PROCESSOR_IDLE) {
1147: // Only wait if the processor is actually processing a command
1148: synchronized (threadSync) {
1149: try {
1150: threadSync.wait(5000);
1151: } catch (InterruptedException e) {
1152: ;
1153: }
1154: }
1155: }
1156: thread = null;
1157:
1158: }
1159:
1160: // ------------------------------------------------------ Lifecycle Methods
1161:
1162: /**
1163: * Add a lifecycle event listener to this component.
1164: *
1165: * @param listener The listener to add
1166: */
1167: public void addLifecycleListener(LifecycleListener listener) {
1168:
1169: lifecycle.addLifecycleListener(listener);
1170:
1171: }
1172:
1173: /**
1174: * Get the lifecycle listeners associated with this lifecycle. If this
1175: * Lifecycle has no listeners registered, a zero-length array is returned.
1176: */
1177: public LifecycleListener[] findLifecycleListeners() {
1178:
1179: return lifecycle.findLifecycleListeners();
1180:
1181: }
1182:
1183: /**
1184: * Remove a lifecycle event listener from this component.
1185: *
1186: * @param listener The listener to add
1187: */
1188: public void removeLifecycleListener(LifecycleListener listener) {
1189:
1190: lifecycle.removeLifecycleListener(listener);
1191:
1192: }
1193:
1194: /**
1195: * Start the background thread we will use for request processing.
1196: *
1197: * @exception LifecycleException if a fatal startup error occurs
1198: */
1199: public void start() throws LifecycleException {
1200:
1201: if (started)
1202: throw new LifecycleException(sm
1203: .getString("httpProcessor.alreadyStarted"));
1204: lifecycle.fireLifecycleEvent(START_EVENT, null);
1205: started = true;
1206:
1207: threadStart();
1208:
1209: }
1210:
1211: /**
1212: * Stop the background thread we will use for request processing.
1213: *
1214: * @exception LifecycleException if a fatal shutdown error occurs
1215: */
1216: public void stop() throws LifecycleException {
1217:
1218: if (!started)
1219: throw new LifecycleException(sm
1220: .getString("httpProcessor.notStarted"));
1221: lifecycle.fireLifecycleEvent(STOP_EVENT, null);
1222: started = false;
1223:
1224: threadStop();
1225:
1226: }
1227:
1228: }
|