0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017:
0018: package org.apache.coyote.http11;
0019:
0020: import java.io.IOException;
0021: import java.io.InterruptedIOException;
0022: import java.net.InetAddress;
0023: import java.nio.channels.SelectionKey;
0024: import java.util.StringTokenizer;
0025: import java.util.regex.Pattern;
0026: import java.util.regex.PatternSyntaxException;
0027:
0028: import org.apache.coyote.ActionCode;
0029: import org.apache.coyote.ActionHook;
0030: import org.apache.coyote.Adapter;
0031: import org.apache.coyote.Request;
0032: import org.apache.coyote.RequestInfo;
0033: import org.apache.coyote.Response;
0034: import org.apache.coyote.http11.filters.BufferedInputFilter;
0035: import org.apache.coyote.http11.filters.ChunkedInputFilter;
0036: import org.apache.coyote.http11.filters.ChunkedOutputFilter;
0037: import org.apache.coyote.http11.filters.GzipOutputFilter;
0038: import org.apache.coyote.http11.filters.IdentityInputFilter;
0039: import org.apache.coyote.http11.filters.IdentityOutputFilter;
0040: import org.apache.coyote.http11.filters.SavedRequestInputFilter;
0041: import org.apache.coyote.http11.filters.VoidInputFilter;
0042: import org.apache.coyote.http11.filters.VoidOutputFilter;
0043: import org.apache.tomcat.util.buf.Ascii;
0044: import org.apache.tomcat.util.buf.ByteChunk;
0045: import org.apache.tomcat.util.buf.HexUtils;
0046: import org.apache.tomcat.util.buf.MessageBytes;
0047: import org.apache.tomcat.util.http.FastHttpDateFormat;
0048: import org.apache.tomcat.util.http.MimeHeaders;
0049: import org.apache.tomcat.util.net.NioChannel;
0050: import org.apache.tomcat.util.net.NioEndpoint;
0051: import org.apache.tomcat.util.net.SSLSupport;
0052: import org.apache.tomcat.util.net.SocketStatus;
0053: import org.apache.tomcat.util.net.NioEndpoint.Handler.SocketState;
0054: import org.apache.tomcat.util.res.StringManager;
0055: import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
0056:
0057: /**
0058: * Processes HTTP requests.
0059: *
0060: * @author Remy Maucherat
0061: * @author Filip Hanik
0062: */
0063: public class Http11NioProcessor implements ActionHook {
0064:
0065: /**
0066: * Logger.
0067: */
0068: protected static org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
0069: .getLog(Http11NioProcessor.class);
0070:
0071: /**
0072: * The string manager for this package.
0073: */
0074: protected static StringManager sm = StringManager
0075: .getManager(Constants.Package);
0076:
0077: /**
0078: * SSL information.
0079: */
0080: protected SSLSupport sslSupport;
0081:
0082: // ----------------------------------------------------------- Constructors
0083:
0084: public Http11NioProcessor(int rxBufSize, int txBufSize,
0085: int maxHttpHeaderSize, NioEndpoint endpoint) {
0086:
0087: this .endpoint = endpoint;
0088:
0089: request = new Request();
0090: int readTimeout = endpoint.getSoTimeout();
0091: inputBuffer = new InternalNioInputBuffer(request,
0092: maxHttpHeaderSize, readTimeout);
0093: request.setInputBuffer(inputBuffer);
0094:
0095: response = new Response();
0096: response.setHook(this );
0097: outputBuffer = new InternalNioOutputBuffer(response,
0098: maxHttpHeaderSize, readTimeout);
0099: response.setOutputBuffer(outputBuffer);
0100: request.setResponse(response);
0101:
0102: ssl = endpoint.isSSLEnabled();
0103:
0104: initializeFilters();
0105:
0106: // Cause loading of HexUtils
0107: int foo = HexUtils.DEC[0];
0108:
0109: // Cause loading of FastHttpDateFormat
0110: FastHttpDateFormat.getCurrentDate();
0111:
0112: }
0113:
0114: // ----------------------------------------------------- Instance Variables
0115:
0116: /**
0117: * Associated adapter.
0118: */
0119: protected Adapter adapter = null;
0120:
0121: /**
0122: * Request object.
0123: */
0124: protected Request request = null;
0125:
0126: /**
0127: * Response object.
0128: */
0129: protected Response response = null;
0130:
0131: /**
0132: * Input.
0133: */
0134: protected InternalNioInputBuffer inputBuffer = null;
0135:
0136: /**
0137: * Output.
0138: */
0139: protected InternalNioOutputBuffer outputBuffer = null;
0140:
0141: /**
0142: * Error flag.
0143: */
0144: protected boolean error = false;
0145:
0146: /**
0147: * Keep-alive.
0148: */
0149: protected boolean keepAlive = true;
0150:
0151: /**
0152: * HTTP/1.1 flag.
0153: */
0154: protected boolean http11 = true;
0155:
0156: /**
0157: * HTTP/0.9 flag.
0158: */
0159: protected boolean http09 = false;
0160:
0161: /**
0162: * Sendfile data.
0163: */
0164: protected NioEndpoint.SendfileData sendfileData = null;
0165:
0166: /**
0167: * Comet used.
0168: */
0169: protected boolean comet = false;
0170:
0171: /**
0172: * Closed flag, a Comet async thread can
0173: * signal for this Nio processor to be closed and recycled instead
0174: * of waiting for a timeout.
0175: * Closed by HttpServletResponse.getWriter().close()
0176: */
0177: protected boolean cometClose = false;
0178:
0179: /**
0180: * Content delimitator for the request (if false, the connection will
0181: * be closed at the end of the request).
0182: */
0183: protected boolean contentDelimitation = true;
0184:
0185: /**
0186: * Is there an expectation ?
0187: */
0188: protected boolean expectation = false;
0189:
0190: /**
0191: * List of restricted user agents.
0192: */
0193: protected Pattern[] restrictedUserAgents = null;
0194:
0195: /**
0196: * Maximum number of Keep-Alive requests to honor.
0197: */
0198: protected int maxKeepAliveRequests = -1;
0199:
0200: /**
0201: * SSL enabled ?
0202: */
0203: protected boolean ssl = false;
0204:
0205: /**
0206: * Socket associated with the current connection.
0207: */
0208: protected NioChannel socket = null;
0209:
0210: /**
0211: * Remote Address associated with the current connection.
0212: */
0213: protected String remoteAddr = null;
0214:
0215: /**
0216: * Remote Host associated with the current connection.
0217: */
0218: protected String remoteHost = null;
0219:
0220: /**
0221: * Local Host associated with the current connection.
0222: */
0223: protected String localName = null;
0224:
0225: /**
0226: * Local port to which the socket is connected
0227: */
0228: protected int localPort = -1;
0229:
0230: /**
0231: * Remote port to which the socket is connected
0232: */
0233: protected int remotePort = -1;
0234:
0235: /**
0236: * The local Host address.
0237: */
0238: protected String localAddr = null;
0239:
0240: /**
0241: * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.
0242: */
0243: protected int timeout = 300000;
0244:
0245: /**
0246: * Flag to disable setting a different time-out on uploads.
0247: */
0248: protected boolean disableUploadTimeout = false;
0249:
0250: /**
0251: * Allowed compression level.
0252: */
0253: protected int compressionLevel = 0;
0254:
0255: /**
0256: * Minimum contentsize to make compression.
0257: */
0258: protected int compressionMinSize = 2048;
0259:
0260: /**
0261: * Socket buffering.
0262: */
0263: protected int socketBuffer = -1;
0264:
0265: /**
0266: * Max save post size.
0267: */
0268: protected int maxSavePostSize = 4 * 1024;
0269:
0270: /**
0271: * List of user agents to not use gzip with
0272: */
0273: protected Pattern noCompressionUserAgents[] = null;
0274:
0275: /**
0276: * List of MIMES which could be gzipped
0277: */
0278: protected String[] compressableMimeTypes = { "text/html",
0279: "text/xml", "text/plain" };
0280:
0281: /**
0282: * Host name (used to avoid useless B2C conversion on the host name).
0283: */
0284: protected char[] hostNameC = new char[0];
0285:
0286: /**
0287: * Associated endpoint.
0288: */
0289: protected NioEndpoint endpoint;
0290:
0291: /**
0292: * Allow a customized the server header for the tin-foil hat folks.
0293: */
0294: protected String server = null;
0295:
0296: // ------------------------------------------------------------- Properties
0297:
0298: /**
0299: * Return compression level.
0300: */
0301: public String getCompression() {
0302: switch (compressionLevel) {
0303: case 0:
0304: return "off";
0305: case 1:
0306: return "on";
0307: case 2:
0308: return "force";
0309: }
0310: return "off";
0311: }
0312:
0313: /**
0314: * Set compression level.
0315: */
0316: public void setCompression(String compression) {
0317: if (compression.equals("on")) {
0318: this .compressionLevel = 1;
0319: } else if (compression.equals("force")) {
0320: this .compressionLevel = 2;
0321: } else if (compression.equals("off")) {
0322: this .compressionLevel = 0;
0323: } else {
0324: try {
0325: // Try to parse compression as an int, which would give the
0326: // minimum compression size
0327: compressionMinSize = Integer.parseInt(compression);
0328: this .compressionLevel = 1;
0329: } catch (Exception e) {
0330: this .compressionLevel = 0;
0331: }
0332: }
0333: }
0334:
0335: /**
0336: * Set Minimum size to trigger compression.
0337: */
0338: public void setCompressionMinSize(int compressionMinSize) {
0339: this .compressionMinSize = compressionMinSize;
0340: }
0341:
0342: /**
0343: * Add user-agent for which gzip compression didn't works
0344: * The user agent String given will be exactly matched
0345: * to the user-agent header submitted by the client.
0346: *
0347: * @param userAgent user-agent string
0348: */
0349: public void addNoCompressionUserAgent(String userAgent) {
0350: try {
0351: Pattern nRule = Pattern.compile(userAgent);
0352: noCompressionUserAgents = addREArray(
0353: noCompressionUserAgents, nRule);
0354: } catch (PatternSyntaxException pse) {
0355: log.error(sm.getString("http11processor.regexp.error",
0356: userAgent), pse);
0357: }
0358: }
0359:
0360: /**
0361: * Set no compression user agent list (this method is best when used with
0362: * a large number of connectors, where it would be better to have all of
0363: * them referenced a single array).
0364: */
0365: public void setNoCompressionUserAgents(
0366: Pattern[] noCompressionUserAgents) {
0367: this .noCompressionUserAgents = noCompressionUserAgents;
0368: }
0369:
0370: /**
0371: * Set no compression user agent list.
0372: * List contains users agents separated by ',' :
0373: *
0374: * ie: "gorilla,desesplorer,tigrus"
0375: */
0376: public void setNoCompressionUserAgents(
0377: String noCompressionUserAgents) {
0378: if (noCompressionUserAgents != null) {
0379: StringTokenizer st = new StringTokenizer(
0380: noCompressionUserAgents, ",");
0381:
0382: while (st.hasMoreTokens()) {
0383: addNoCompressionUserAgent(st.nextToken().trim());
0384: }
0385: }
0386: }
0387:
0388: /**
0389: * Add a mime-type which will be compressable
0390: * The mime-type String will be exactly matched
0391: * in the response mime-type header .
0392: *
0393: * @param mimeType mime-type string
0394: */
0395: public void addCompressableMimeType(String mimeType) {
0396: compressableMimeTypes = addStringArray(compressableMimeTypes,
0397: mimeType);
0398: }
0399:
0400: /**
0401: * Set compressable mime-type list (this method is best when used with
0402: * a large number of connectors, where it would be better to have all of
0403: * them referenced a single array).
0404: */
0405: public void setCompressableMimeTypes(String[] compressableMimeTypes) {
0406: this .compressableMimeTypes = compressableMimeTypes;
0407: }
0408:
0409: /**
0410: * Set compressable mime-type list
0411: * List contains users agents separated by ',' :
0412: *
0413: * ie: "text/html,text/xml,text/plain"
0414: */
0415: public void setCompressableMimeTypes(String compressableMimeTypes) {
0416: if (compressableMimeTypes != null) {
0417: StringTokenizer st = new StringTokenizer(
0418: compressableMimeTypes, ",");
0419:
0420: while (st.hasMoreTokens()) {
0421: addCompressableMimeType(st.nextToken().trim());
0422: }
0423: }
0424: }
0425:
0426: /**
0427: * Return the list of restricted user agents.
0428: */
0429: public String[] findCompressableMimeTypes() {
0430: return (compressableMimeTypes);
0431: }
0432:
0433: // --------------------------------------------------------- Public Methods
0434:
0435: /**
0436: * Add input or output filter.
0437: *
0438: * @param className class name of the filter
0439: */
0440: protected void addFilter(String className) {
0441: try {
0442: Class clazz = Class.forName(className);
0443: Object obj = clazz.newInstance();
0444: if (obj instanceof InputFilter) {
0445: inputBuffer.addFilter((InputFilter) obj);
0446: } else if (obj instanceof OutputFilter) {
0447: outputBuffer.addFilter((OutputFilter) obj);
0448: } else {
0449: log.warn(sm.getString("http11processor.filter.unknown",
0450: className));
0451: }
0452: } catch (Exception e) {
0453: log.error(sm.getString("http11processor.filter.error",
0454: className), e);
0455: }
0456: }
0457:
0458: /**
0459: * General use method
0460: *
0461: * @param sArray the StringArray
0462: * @param value string
0463: */
0464: private String[] addStringArray(String sArray[], String value) {
0465: String[] result = null;
0466: if (sArray == null) {
0467: result = new String[1];
0468: result[0] = value;
0469: } else {
0470: result = new String[sArray.length + 1];
0471: for (int i = 0; i < sArray.length; i++)
0472: result[i] = sArray[i];
0473: result[sArray.length] = value;
0474: }
0475: return result;
0476: }
0477:
0478: /**
0479: * General use method
0480: *
0481: * @param rArray the REArray
0482: * @param value Obj
0483: */
0484: private Pattern[] addREArray(Pattern rArray[], Pattern value) {
0485: Pattern[] result = null;
0486: if (rArray == null) {
0487: result = new Pattern[1];
0488: result[0] = value;
0489: } else {
0490: result = new Pattern[rArray.length + 1];
0491: for (int i = 0; i < rArray.length; i++)
0492: result[i] = rArray[i];
0493: result[rArray.length] = value;
0494: }
0495: return result;
0496: }
0497:
0498: /**
0499: * General use method
0500: *
0501: * @param sArray the StringArray
0502: * @param value string
0503: */
0504: private boolean inStringArray(String sArray[], String value) {
0505: for (int i = 0; i < sArray.length; i++) {
0506: if (sArray[i].equals(value)) {
0507: return true;
0508: }
0509: }
0510: return false;
0511: }
0512:
0513: /**
0514: * Checks if any entry in the string array starts with the specified value
0515: *
0516: * @param sArray the StringArray
0517: * @param value string
0518: */
0519: private boolean startsWithStringArray(String sArray[], String value) {
0520: if (value == null)
0521: return false;
0522: for (int i = 0; i < sArray.length; i++) {
0523: if (value.startsWith(sArray[i])) {
0524: return true;
0525: }
0526: }
0527: return false;
0528: }
0529:
0530: /**
0531: * Add restricted user-agent (which will downgrade the connector
0532: * to HTTP/1.0 mode). The user agent String given will be matched
0533: * via regexp to the user-agent header submitted by the client.
0534: *
0535: * @param userAgent user-agent string
0536: */
0537: public void addRestrictedUserAgent(String userAgent) {
0538: try {
0539: Pattern nRule = Pattern.compile(userAgent);
0540: restrictedUserAgents = addREArray(restrictedUserAgents,
0541: nRule);
0542: } catch (PatternSyntaxException pse) {
0543: log.error(sm.getString("http11processor.regexp.error",
0544: userAgent), pse);
0545: }
0546: }
0547:
0548: /**
0549: * Set restricted user agent list (this method is best when used with
0550: * a large number of connectors, where it would be better to have all of
0551: * them referenced a single array).
0552: */
0553: public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) {
0554: this .restrictedUserAgents = restrictedUserAgents;
0555: }
0556:
0557: /**
0558: * Set restricted user agent list (which will downgrade the connector
0559: * to HTTP/1.0 mode). List contains users agents separated by ',' :
0560: *
0561: * ie: "gorilla,desesplorer,tigrus"
0562: */
0563: public void setRestrictedUserAgents(String restrictedUserAgents) {
0564: if (restrictedUserAgents != null) {
0565: StringTokenizer st = new StringTokenizer(
0566: restrictedUserAgents, ",");
0567: while (st.hasMoreTokens()) {
0568: addRestrictedUserAgent(st.nextToken().trim());
0569: }
0570: }
0571: }
0572:
0573: /**
0574: * Return the list of restricted user agents.
0575: */
0576: public String[] findRestrictedUserAgents() {
0577: String[] sarr = new String[restrictedUserAgents.length];
0578:
0579: for (int i = 0; i < restrictedUserAgents.length; i++)
0580: sarr[i] = restrictedUserAgents[i].toString();
0581:
0582: return (sarr);
0583: }
0584:
0585: /**
0586: * Set the maximum number of Keep-Alive requests to honor.
0587: * This is to safeguard from DoS attacks. Setting to a negative
0588: * value disables the check.
0589: */
0590: public void setMaxKeepAliveRequests(int mkar) {
0591: maxKeepAliveRequests = mkar;
0592: }
0593:
0594: /**
0595: * Return the number of Keep-Alive requests that we will honor.
0596: */
0597: public int getMaxKeepAliveRequests() {
0598: return maxKeepAliveRequests;
0599: }
0600:
0601: /**
0602: * Set the maximum size of a POST which will be buffered in SSL mode.
0603: */
0604: public void setMaxSavePostSize(int msps) {
0605: maxSavePostSize = msps;
0606: }
0607:
0608: /**
0609: * Return the maximum size of a POST which will be buffered in SSL mode.
0610: */
0611: public int getMaxSavePostSize() {
0612: return maxSavePostSize;
0613: }
0614:
0615: /**
0616: * Set the flag to control upload time-outs.
0617: */
0618: public void setDisableUploadTimeout(boolean isDisabled) {
0619: disableUploadTimeout = isDisabled;
0620: }
0621:
0622: /**
0623: * Get the flag that controls upload time-outs.
0624: */
0625: public boolean getDisableUploadTimeout() {
0626: return disableUploadTimeout;
0627: }
0628:
0629: /**
0630: * Set the socket buffer flag.
0631: */
0632: public void setSocketBuffer(int socketBuffer) {
0633: this .socketBuffer = socketBuffer;
0634: outputBuffer.setSocketBuffer(socketBuffer);
0635: }
0636:
0637: /**
0638: * Get the socket buffer flag.
0639: */
0640: public int getSocketBuffer() {
0641: return socketBuffer;
0642: }
0643:
0644: /**
0645: * Set the upload timeout.
0646: */
0647: public void setTimeout(int timeouts) {
0648: timeout = timeouts;
0649: }
0650:
0651: /**
0652: * Get the upload timeout.
0653: */
0654: public int getTimeout() {
0655: return timeout;
0656: }
0657:
0658: /**
0659: * Set the server header name.
0660: */
0661: public void setServer(String server) {
0662: if (server == null || server.equals("")) {
0663: this .server = null;
0664: } else {
0665: this .server = server;
0666: }
0667: }
0668:
0669: /**
0670: * Get the server header name.
0671: */
0672: public String getServer() {
0673: return server;
0674: }
0675:
0676: /** Get the request associated with this processor.
0677: *
0678: * @return The request
0679: */
0680: public Request getRequest() {
0681: return request;
0682: }
0683:
0684: /**
0685: * Process pipelined HTTP requests using the specified input and output
0686: * streams.
0687: *
0688: * @throws IOException error during an I/O operation
0689: */
0690: public SocketState event(SocketStatus status) throws IOException {
0691:
0692: RequestInfo rp = request.getRequestProcessor();
0693:
0694: try {
0695: rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
0696: error = !adapter.event(request, response, status);
0697: if (!error) {
0698: NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) socket
0699: .getAttachment(false);
0700: if (attach != null) {
0701: attach.setComet(comet);
0702: if (comet) {
0703: Integer comettimeout = (Integer) request
0704: .getAttribute("org.apache.tomcat.comet.timeout");
0705: if (comettimeout != null)
0706: attach.setTimeout(comettimeout.longValue());
0707: } else {
0708: //reset the timeout
0709: attach.setTimeout(endpoint
0710: .getSocketProperties().getSoTimeout());
0711: }
0712:
0713: }
0714: }
0715: } catch (InterruptedIOException e) {
0716: error = true;
0717: } catch (Throwable t) {
0718: log.error(sm.getString("http11processor.request.process"),
0719: t);
0720: // 500 - Internal Server Error
0721: response.setStatus(500);
0722: error = true;
0723: }
0724:
0725: rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
0726:
0727: if (error) {
0728: recycle();
0729: return SocketState.CLOSED;
0730: } else if (!comet) {
0731: recycle();
0732: return SocketState.OPEN;
0733: } else {
0734: return SocketState.LONG;
0735: }
0736: }
0737:
0738: /**
0739: * Process pipelined HTTP requests using the specified input and output
0740: * streams.
0741: *
0742: * @throws IOException error during an I/O operation
0743: */
0744: public SocketState process(NioChannel socket) throws IOException {
0745: RequestInfo rp = request.getRequestProcessor();
0746: rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
0747:
0748: // Set the remote address
0749: remoteAddr = null;
0750: remoteHost = null;
0751: localAddr = null;
0752: localName = null;
0753: remotePort = -1;
0754: localPort = -1;
0755:
0756: // Setting up the socket
0757: this .socket = socket;
0758: inputBuffer.setSocket(socket);
0759: outputBuffer.setSocket(socket);
0760: inputBuffer.setSelectorPool(endpoint.getSelectorPool());
0761: outputBuffer.setSelectorPool(endpoint.getSelectorPool());
0762:
0763: // Error flag
0764: error = false;
0765: keepAlive = true;
0766: comet = false;
0767:
0768: int keepAliveLeft = maxKeepAliveRequests;
0769: long soTimeout = endpoint.getSoTimeout();
0770:
0771: int limit = 0;
0772:
0773: boolean keptAlive = false;
0774: boolean openSocket = false;
0775: boolean recycle = true;
0776: while (!error && keepAlive && !comet) {
0777:
0778: // Parsing the request header
0779: try {
0780: if (!disableUploadTimeout && keptAlive && soTimeout > 0) {
0781: socket.getIOChannel().socket().setSoTimeout(
0782: (int) soTimeout);
0783: inputBuffer.readTimeout = soTimeout;
0784: }
0785: if (!inputBuffer.parseRequestLine(keptAlive
0786: && (endpoint.getCurrentThreadsBusy() >= limit))) {
0787: // This means that no data is available right now
0788: // (long keepalive), so that the processor should be recycled
0789: // and the method should return true
0790: openSocket = true;
0791: // Add the socket to the poller
0792: socket.getPoller().add(socket);
0793: break;
0794: }
0795: keptAlive = true;
0796: if (!inputBuffer.parseHeaders()) {
0797: openSocket = true;
0798: socket.getPoller().add(socket);
0799: recycle = false;
0800: break;
0801: }
0802: request.setStartTime(System.currentTimeMillis());
0803: if (!disableUploadTimeout) { //only for body, not for request headers
0804: socket.getIOChannel().socket().setSoTimeout(
0805: (int) timeout);
0806: inputBuffer.readTimeout = soTimeout;
0807: }
0808: } catch (IOException e) {
0809: error = true;
0810: break;
0811: } catch (Throwable t) {
0812: if (log.isDebugEnabled()) {
0813: log.debug(sm
0814: .getString("http11processor.header.parse"),
0815: t);
0816: }
0817: // 400 - Bad Request
0818: response.setStatus(400);
0819: error = true;
0820: }
0821:
0822: // Setting up filters, and parse some request headers
0823: rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
0824: try {
0825: prepareRequest();
0826: } catch (Throwable t) {
0827: if (log.isDebugEnabled()) {
0828: log
0829: .debug(
0830: sm
0831: .getString("http11processor.request.prepare"),
0832: t);
0833: }
0834: // 400 - Internal Server Error
0835: response.setStatus(400);
0836: error = true;
0837: }
0838:
0839: if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)
0840: keepAlive = false;
0841:
0842: // Process the request in the adapter
0843: if (!error) {
0844: try {
0845: rp
0846: .setStage(org.apache.coyote.Constants.STAGE_SERVICE);
0847: adapter.service(request, response);
0848: // Handle when the response was committed before a serious
0849: // error occurred. Throwing a ServletException should both
0850: // set the status to 500 and set the errorException.
0851: // If we fail here, then the response is likely already
0852: // committed, so we can't try and set headers.
0853: if (keepAlive && !error) { // Avoid checking twice.
0854: error = response.getErrorException() != null
0855: || statusDropsConnection(response
0856: .getStatus());
0857: }
0858: // Comet support
0859: SelectionKey key = socket.getIOChannel().keyFor(
0860: socket.getPoller().getSelector());
0861: if (key != null) {
0862: NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key
0863: .attachment();
0864: if (attach != null) {
0865: attach.setComet(comet);
0866: if (comet) {
0867: Integer comettimeout = (Integer) request
0868: .getAttribute("org.apache.tomcat.comet.timeout");
0869: if (comettimeout != null)
0870: attach.setTimeout(comettimeout
0871: .longValue());
0872: }
0873: }
0874: }
0875: } catch (InterruptedIOException e) {
0876: error = true;
0877: } catch (Throwable t) {
0878: log
0879: .error(
0880: sm
0881: .getString("http11processor.request.process"),
0882: t);
0883: // 500 - Internal Server Error
0884: response.setStatus(500);
0885: error = true;
0886: }
0887: }
0888:
0889: // Finish the handling of the request
0890: if (!comet) {
0891: endRequest();
0892: }
0893:
0894: // If there was an error, make sure the request is counted as
0895: // and error, and update the statistics counter
0896: if (error) {
0897: response.setStatus(500);
0898: }
0899: request.updateCounters();
0900:
0901: if (!comet) {
0902: // Next request
0903: inputBuffer.nextRequest();
0904: outputBuffer.nextRequest();
0905: }
0906:
0907: // Do sendfile as needed: add socket to sendfile and end
0908: if (sendfileData != null && !error) {
0909: KeyAttachment ka = (KeyAttachment) socket
0910: .getAttachment(false);
0911: ka.setSendfileData(sendfileData);
0912: sendfileData.keepAlive = keepAlive;
0913: SelectionKey key = socket.getIOChannel().keyFor(
0914: socket.getPoller().getSelector());
0915: //do the first write on this thread, might as well
0916: openSocket = socket.getPoller().processSendfile(key,
0917: ka, true);
0918: break;
0919: }
0920:
0921: rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
0922:
0923: }
0924:
0925: rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
0926:
0927: if (comet) {
0928: if (error) {
0929: recycle();
0930: return SocketState.CLOSED;
0931: } else {
0932: return SocketState.LONG;
0933: }
0934: } else {
0935: if (recycle)
0936: recycle();
0937: return (openSocket) ? SocketState.OPEN : SocketState.CLOSED;
0938: }
0939:
0940: }
0941:
0942: public void endRequest() {
0943:
0944: // Finish the handling of the request
0945: try {
0946: inputBuffer.endRequest();
0947: } catch (IOException e) {
0948: error = true;
0949: } catch (Throwable t) {
0950: log
0951: .error(
0952: sm
0953: .getString("http11processor.request.finish"),
0954: t);
0955: // 500 - Internal Server Error
0956: response.setStatus(500);
0957: error = true;
0958: }
0959: try {
0960: outputBuffer.endRequest();
0961: } catch (IOException e) {
0962: error = true;
0963: } catch (Throwable t) {
0964: log.error(sm.getString("http11processor.response.finish"),
0965: t);
0966: error = true;
0967: }
0968:
0969: }
0970:
0971: public void recycle() {
0972: inputBuffer.recycle();
0973: outputBuffer.recycle();
0974: this .socket = null;
0975: this .cometClose = false;
0976: this .comet = false;
0977: }
0978:
0979: // ----------------------------------------------------- ActionHook Methods
0980:
0981: /**
0982: * Send an action to the connector.
0983: *
0984: * @param actionCode Type of the action
0985: * @param param Action parameter
0986: */
0987: public void action(ActionCode actionCode, Object param) {
0988:
0989: if (actionCode == ActionCode.ACTION_COMMIT) {
0990: // Commit current response
0991:
0992: if (response.isCommitted())
0993: return;
0994:
0995: // Validate and write response headers
0996:
0997: try {
0998: prepareResponse();
0999: outputBuffer.commit();
1000: } catch (IOException e) {
1001: // Set error flag
1002: error = true;
1003: }
1004:
1005: } else if (actionCode == ActionCode.ACTION_ACK) {
1006:
1007: // Acknowlege request
1008:
1009: // Send a 100 status back if it makes sense (response not committed
1010: // yet, and client specified an expectation for 100-continue)
1011:
1012: if ((response.isCommitted()) || !expectation)
1013: return;
1014:
1015: inputBuffer.setSwallowInput(true);
1016: try {
1017: outputBuffer.sendAck();
1018: } catch (IOException e) {
1019: // Set error flag
1020: error = true;
1021: }
1022:
1023: } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
1024:
1025: try {
1026: outputBuffer.flush();
1027: } catch (IOException e) {
1028: // Set error flag
1029: error = true;
1030: response.setErrorException(e);
1031: }
1032:
1033: } else if (actionCode == ActionCode.ACTION_CLOSE) {
1034: // Close
1035:
1036: // End the processing of the current request, and stop any further
1037: // transactions with the client
1038:
1039: comet = false;
1040: cometClose = true;
1041: SelectionKey key = socket.getIOChannel().keyFor(
1042: socket.getPoller().getSelector());
1043: if (key != null) {
1044: NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key
1045: .attachment();
1046: if (attach != null && attach.getComet()) {
1047: //if this is a comet connection
1048: //then execute the connection closure at the next selector loop
1049: request.getAttributes().remove(
1050: "org.apache.tomcat.comet.timeout");
1051: //attach.setTimeout(5000); //force a cleanup in 5 seconds
1052: //attach.setError(true); //this has caused concurrency errors
1053: }
1054: }
1055:
1056: try {
1057: outputBuffer.endRequest();
1058: } catch (IOException e) {
1059: // Set error flag
1060: error = true;
1061: }
1062:
1063: } else if (actionCode == ActionCode.ACTION_RESET) {
1064:
1065: // Reset response
1066:
1067: // Note: This must be called before the response is committed
1068:
1069: outputBuffer.reset();
1070:
1071: } else if (actionCode == ActionCode.ACTION_CUSTOM) {
1072:
1073: // Do nothing
1074:
1075: } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
1076:
1077: // Get remote host address
1078: if ((remoteAddr == null) && (socket != null)) {
1079: InetAddress inetAddr = socket.getIOChannel().socket()
1080: .getInetAddress();
1081: if (inetAddr != null) {
1082: remoteAddr = inetAddr.getHostAddress();
1083: }
1084: }
1085: request.remoteAddr().setString(remoteAddr);
1086:
1087: } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
1088:
1089: // Get local host name
1090: if ((localName == null) && (socket != null)) {
1091: InetAddress inetAddr = socket.getIOChannel().socket()
1092: .getLocalAddress();
1093: if (inetAddr != null) {
1094: localName = inetAddr.getHostName();
1095: }
1096: }
1097: request.localName().setString(localName);
1098:
1099: } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
1100:
1101: // Get remote host name
1102: if ((remoteHost == null) && (socket != null)) {
1103: InetAddress inetAddr = socket.getIOChannel().socket()
1104: .getInetAddress();
1105: if (inetAddr != null) {
1106: remoteHost = inetAddr.getHostName();
1107: }
1108: if (remoteHost == null) {
1109: if (remoteAddr != null) {
1110: remoteHost = remoteAddr;
1111: } else { // all we can do is punt
1112: request.remoteHost().recycle();
1113: }
1114: }
1115: }
1116: request.remoteHost().setString(remoteHost);
1117:
1118: } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
1119:
1120: if (localAddr == null)
1121: localAddr = socket.getIOChannel().socket()
1122: .getLocalAddress().getHostAddress();
1123:
1124: request.localAddr().setString(localAddr);
1125:
1126: } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
1127:
1128: if ((remotePort == -1) && (socket != null)) {
1129: remotePort = socket.getIOChannel().socket().getPort();
1130: }
1131: request.setRemotePort(remotePort);
1132:
1133: } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
1134:
1135: if ((localPort == -1) && (socket != null)) {
1136: localPort = socket.getIOChannel().socket()
1137: .getLocalPort();
1138: }
1139: request.setLocalPort(localPort);
1140:
1141: } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE) {
1142:
1143: try {
1144: if (sslSupport != null) {
1145: Object sslO = sslSupport.getCipherSuite();
1146: if (sslO != null)
1147: request.setAttribute(
1148: SSLSupport.CIPHER_SUITE_KEY, sslO);
1149: sslO = sslSupport.getPeerCertificateChain(false);
1150: if (sslO != null)
1151: request.setAttribute(
1152: SSLSupport.CERTIFICATE_KEY, sslO);
1153: sslO = sslSupport.getKeySize();
1154: if (sslO != null)
1155: request.setAttribute(SSLSupport.KEY_SIZE_KEY,
1156: sslO);
1157: sslO = sslSupport.getSessionId();
1158: if (sslO != null)
1159: request.setAttribute(SSLSupport.SESSION_ID_KEY,
1160: sslO);
1161: }
1162: } catch (Exception e) {
1163: log.warn(sm.getString("http11processor.socket.ssl"), e);
1164: }
1165:
1166: } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
1167:
1168: if (sslSupport != null) {
1169: /*
1170: * Consume and buffer the request body, so that it does not
1171: * interfere with the client's handshake messages
1172: */
1173: InputFilter[] inputFilters = inputBuffer.getFilters();
1174: ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
1175: .setLimit(maxSavePostSize);
1176: inputBuffer
1177: .addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
1178: try {
1179: Object sslO = sslSupport
1180: .getPeerCertificateChain(true);
1181: if (sslO != null) {
1182: request.setAttribute(
1183: SSLSupport.CERTIFICATE_KEY, sslO);
1184: }
1185: } catch (Exception e) {
1186: log.warn(
1187: sm.getString("http11processor.socket.ssl"),
1188: e);
1189: }
1190: }
1191:
1192: } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
1193: ByteChunk body = (ByteChunk) param;
1194:
1195: InputFilter savedBody = new SavedRequestInputFilter(body);
1196: savedBody.setRequest(request);
1197:
1198: InternalNioInputBuffer internalBuffer = (InternalNioInputBuffer) request
1199: .getInputBuffer();
1200: internalBuffer.addActiveFilter(savedBody);
1201:
1202: } else if (actionCode == ActionCode.ACTION_AVAILABLE) {
1203: request.setAvailable(inputBuffer.available());
1204: } else if (actionCode == ActionCode.ACTION_COMET_BEGIN) {
1205: comet = true;
1206: } else if (actionCode == ActionCode.ACTION_COMET_END) {
1207: comet = false;
1208: }
1209:
1210: }
1211:
1212: // ------------------------------------------------------ Connector Methods
1213:
1214: /**
1215: * Set the associated adapter.
1216: *
1217: * @param adapter the new adapter
1218: */
1219: public void setAdapter(Adapter adapter) {
1220: this .adapter = adapter;
1221: }
1222:
1223: public void setSslSupport(SSLSupport sslSupport) {
1224: this .sslSupport = sslSupport;
1225: }
1226:
1227: /**
1228: * Get the associated adapter.
1229: *
1230: * @return the associated adapter
1231: */
1232: public Adapter getAdapter() {
1233: return adapter;
1234: }
1235:
1236: public SSLSupport getSslSupport() {
1237: return sslSupport;
1238: }
1239:
1240: // ------------------------------------------------------ Protected Methods
1241:
1242: /**
1243: * After reading the request headers, we have to setup the request filters.
1244: */
1245: protected void prepareRequest() {
1246:
1247: http11 = true;
1248: http09 = false;
1249: contentDelimitation = false;
1250: expectation = false;
1251: sendfileData = null;
1252: if (ssl) {
1253: request.scheme().setString("https");
1254: }
1255: MessageBytes protocolMB = request.protocol();
1256: if (protocolMB.equals(Constants.HTTP_11)) {
1257: http11 = true;
1258: protocolMB.setString(Constants.HTTP_11);
1259: } else if (protocolMB.equals(Constants.HTTP_10)) {
1260: http11 = false;
1261: keepAlive = false;
1262: protocolMB.setString(Constants.HTTP_10);
1263: } else if (protocolMB.equals("")) {
1264: // HTTP/0.9
1265: http09 = true;
1266: http11 = false;
1267: keepAlive = false;
1268: } else {
1269: // Unsupported protocol
1270: http11 = false;
1271: error = true;
1272: // Send 505; Unsupported HTTP version
1273: response.setStatus(505);
1274: }
1275:
1276: MessageBytes methodMB = request.method();
1277: if (methodMB.equals(Constants.GET)) {
1278: methodMB.setString(Constants.GET);
1279: } else if (methodMB.equals(Constants.POST)) {
1280: methodMB.setString(Constants.POST);
1281: }
1282:
1283: MimeHeaders headers = request.getMimeHeaders();
1284:
1285: // Check connection header
1286: MessageBytes connectionValueMB = headers.getValue("connection");
1287: if (connectionValueMB != null) {
1288: ByteChunk connectionValueBC = connectionValueMB
1289: .getByteChunk();
1290: if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {
1291: keepAlive = false;
1292: } else if (findBytes(connectionValueBC,
1293: Constants.KEEPALIVE_BYTES) != -1) {
1294: keepAlive = true;
1295: }
1296: }
1297:
1298: MessageBytes expectMB = null;
1299: if (http11)
1300: expectMB = headers.getValue("expect");
1301: if ((expectMB != null)
1302: && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {
1303: inputBuffer.setSwallowInput(false);
1304: expectation = true;
1305: }
1306:
1307: // Check user-agent header
1308: if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {
1309: MessageBytes userAgentValueMB = headers
1310: .getValue("user-agent");
1311: // Check in the restricted list, and adjust the http11
1312: // and keepAlive flags accordingly
1313: if (userAgentValueMB != null) {
1314: String userAgentValue = userAgentValueMB.toString();
1315: for (int i = 0; i < restrictedUserAgents.length; i++) {
1316: if (restrictedUserAgents[i].matcher(userAgentValue)
1317: .matches()) {
1318: http11 = false;
1319: keepAlive = false;
1320: break;
1321: }
1322: }
1323: }
1324: }
1325:
1326: // Check for a full URI (including protocol://host:port/)
1327: ByteChunk uriBC = request.requestURI().getByteChunk();
1328: if (uriBC.startsWithIgnoreCase("http", 0)) {
1329:
1330: int pos = uriBC.indexOf("://", 0, 3, 4);
1331: int uriBCStart = uriBC.getStart();
1332: int slashPos = -1;
1333: if (pos != -1) {
1334: byte[] uriB = uriBC.getBytes();
1335: slashPos = uriBC.indexOf('/', pos + 3);
1336: if (slashPos == -1) {
1337: slashPos = uriBC.getLength();
1338: // Set URI as "/"
1339: request.requestURI().setBytes(uriB,
1340: uriBCStart + pos + 1, 1);
1341: } else {
1342: request.requestURI().setBytes(uriB,
1343: uriBCStart + slashPos,
1344: uriBC.getLength() - slashPos);
1345: }
1346: MessageBytes hostMB = headers.setValue("host");
1347: hostMB.setBytes(uriB, uriBCStart + pos + 3, slashPos
1348: - pos - 3);
1349: }
1350:
1351: }
1352:
1353: // Input filter setup
1354: InputFilter[] inputFilters = inputBuffer.getFilters();
1355:
1356: // Parse transfer-encoding header
1357: MessageBytes transferEncodingValueMB = null;
1358: if (http11)
1359: transferEncodingValueMB = headers
1360: .getValue("transfer-encoding");
1361: if (transferEncodingValueMB != null) {
1362: String transferEncodingValue = transferEncodingValueMB
1363: .toString();
1364: // Parse the comma separated list. "identity" codings are ignored
1365: int startPos = 0;
1366: int commaPos = transferEncodingValue.indexOf(',');
1367: String encodingName = null;
1368: while (commaPos != -1) {
1369: encodingName = transferEncodingValue.substring(
1370: startPos, commaPos).toLowerCase().trim();
1371: if (!addInputFilter(inputFilters, encodingName)) {
1372: // Unsupported transfer encoding
1373: error = true;
1374: // 501 - Unimplemented
1375: response.setStatus(501);
1376: }
1377: startPos = commaPos + 1;
1378: commaPos = transferEncodingValue.indexOf(',', startPos);
1379: }
1380: encodingName = transferEncodingValue.substring(startPos)
1381: .toLowerCase().trim();
1382: if (!addInputFilter(inputFilters, encodingName)) {
1383: // Unsupported transfer encoding
1384: error = true;
1385: // 501 - Unimplemented
1386: response.setStatus(501);
1387: }
1388: }
1389:
1390: // Parse content-length header
1391: long contentLength = request.getContentLengthLong();
1392: if (contentLength >= 0 && !contentDelimitation) {
1393: inputBuffer
1394: .addActiveFilter(inputFilters[Constants.IDENTITY_FILTER]);
1395: contentDelimitation = true;
1396: }
1397:
1398: MessageBytes valueMB = headers.getValue("host");
1399:
1400: // Check host header
1401: if (http11 && (valueMB == null)) {
1402: error = true;
1403: // 400 - Bad request
1404: response.setStatus(400);
1405: }
1406:
1407: parseHost(valueMB);
1408:
1409: if (!contentDelimitation) {
1410: // If there's no content length
1411: // (broken HTTP/1.0 or HTTP/1.1), assume
1412: // the client is not broken and didn't send a body
1413: inputBuffer
1414: .addActiveFilter(inputFilters[Constants.VOID_FILTER]);
1415: contentDelimitation = true;
1416: }
1417:
1418: // Advertise sendfile support through a request attribute
1419: if (endpoint.getUseSendfile())
1420: request.setAttribute("org.apache.tomcat.sendfile.support",
1421: Boolean.TRUE);
1422: // Advertise comet support through a request attribute
1423: request.setAttribute("org.apache.tomcat.comet.support",
1424: Boolean.TRUE);
1425: // Advertise comet timeout support
1426: request.setAttribute("org.apache.tomcat.comet.timeout.support",
1427: Boolean.TRUE);
1428:
1429: }
1430:
1431: /**
1432: * Parse host.
1433: */
1434: public void parseHost(MessageBytes valueMB) {
1435:
1436: if (valueMB == null || valueMB.isNull()) {
1437: // HTTP/1.0
1438: // Default is what the socket tells us. Overriden if a host is
1439: // found/parsed
1440: request.setServerPort(endpoint.getPort());
1441: return;
1442: }
1443:
1444: ByteChunk valueBC = valueMB.getByteChunk();
1445: byte[] valueB = valueBC.getBytes();
1446: int valueL = valueBC.getLength();
1447: int valueS = valueBC.getStart();
1448: int colonPos = -1;
1449: if (hostNameC.length < valueL) {
1450: hostNameC = new char[valueL];
1451: }
1452:
1453: boolean ipv6 = (valueB[valueS] == '[');
1454: boolean bracketClosed = false;
1455: for (int i = 0; i < valueL; i++) {
1456: char b = (char) valueB[i + valueS];
1457: hostNameC[i] = b;
1458: if (b == ']') {
1459: bracketClosed = true;
1460: } else if (b == ':') {
1461: if (!ipv6 || bracketClosed) {
1462: colonPos = i;
1463: break;
1464: }
1465: }
1466: }
1467:
1468: if (colonPos < 0) {
1469: if (!ssl) {
1470: // 80 - Default HTTP port
1471: request.setServerPort(80);
1472: } else {
1473: // 443 - Default HTTPS port
1474: request.setServerPort(443);
1475: }
1476: request.serverName().setChars(hostNameC, 0, valueL);
1477: } else {
1478:
1479: request.serverName().setChars(hostNameC, 0, colonPos);
1480:
1481: int port = 0;
1482: int mult = 1;
1483: for (int i = valueL - 1; i > colonPos; i--) {
1484: int charValue = HexUtils.DEC[(int) valueB[i + valueS]];
1485: if (charValue == -1) {
1486: // Invalid character
1487: error = true;
1488: // 400 - Bad request
1489: response.setStatus(400);
1490: break;
1491: }
1492: port = port + (charValue * mult);
1493: mult = 10 * mult;
1494: }
1495: request.setServerPort(port);
1496:
1497: }
1498:
1499: }
1500:
1501: /**
1502: * Check for compression
1503: */
1504: private boolean isCompressable() {
1505:
1506: // Nope Compression could works in HTTP 1.0 also
1507: // cf: mod_deflate
1508:
1509: // Compression only since HTTP 1.1
1510: // if (! http11)
1511: // return false;
1512:
1513: // Check if browser support gzip encoding
1514: MessageBytes acceptEncodingMB = request.getMimeHeaders()
1515: .getValue("accept-encoding");
1516:
1517: if ((acceptEncodingMB == null)
1518: || (acceptEncodingMB.indexOf("gzip") == -1))
1519: return false;
1520:
1521: // Check if content is not allready gzipped
1522: MessageBytes contentEncodingMB = response.getMimeHeaders()
1523: .getValue("Content-Encoding");
1524:
1525: if ((contentEncodingMB != null)
1526: && (contentEncodingMB.indexOf("gzip") != -1))
1527: return false;
1528:
1529: // If force mode, allways compress (test purposes only)
1530: if (compressionLevel == 2)
1531: return true;
1532:
1533: // Check for incompatible Browser
1534: if (noCompressionUserAgents != null) {
1535: MessageBytes userAgentValueMB = request.getMimeHeaders()
1536: .getValue("user-agent");
1537: if (userAgentValueMB != null) {
1538: String userAgentValue = userAgentValueMB.toString();
1539:
1540: // If one Regexp rule match, disable compression
1541: for (int i = 0; i < noCompressionUserAgents.length; i++)
1542: if (noCompressionUserAgents[i].matcher(
1543: userAgentValue).matches())
1544: return false;
1545: }
1546: }
1547:
1548: // Check if suffisant len to trig the compression
1549: long contentLength = response.getContentLengthLong();
1550: if ((contentLength == -1)
1551: || (contentLength > compressionMinSize)) {
1552: // Check for compatible MIME-TYPE
1553: if (compressableMimeTypes != null) {
1554: return (startsWithStringArray(compressableMimeTypes,
1555: response.getContentType()));
1556: }
1557: }
1558:
1559: return false;
1560: }
1561:
1562: /**
1563: * When committing the response, we have to validate the set of headers, as
1564: * well as setup the response filters.
1565: */
1566: protected void prepareResponse() throws IOException {
1567:
1568: boolean entityBody = true;
1569: contentDelimitation = false;
1570:
1571: OutputFilter[] outputFilters = outputBuffer.getFilters();
1572:
1573: if (http09 == true) {
1574: // HTTP/0.9
1575: outputBuffer
1576: .addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
1577: return;
1578: }
1579:
1580: int statusCode = response.getStatus();
1581: if ((statusCode == 204) || (statusCode == 205)
1582: || (statusCode == 304)) {
1583: // No entity body
1584: outputBuffer
1585: .addActiveFilter(outputFilters[Constants.VOID_FILTER]);
1586: entityBody = false;
1587: contentDelimitation = true;
1588: }
1589:
1590: MessageBytes methodMB = request.method();
1591: if (methodMB.equals("HEAD")) {
1592: // No entity body
1593: outputBuffer
1594: .addActiveFilter(outputFilters[Constants.VOID_FILTER]);
1595: contentDelimitation = true;
1596: }
1597:
1598: // Sendfile support
1599: if (this .endpoint.getUseSendfile()) {
1600: String fileName = (String) request
1601: .getAttribute("org.apache.tomcat.sendfile.filename");
1602: if (fileName != null) {
1603: // No entity body sent here
1604: outputBuffer
1605: .addActiveFilter(outputFilters[Constants.VOID_FILTER]);
1606: contentDelimitation = true;
1607: sendfileData = new NioEndpoint.SendfileData();
1608: sendfileData.fileName = fileName;
1609: sendfileData.pos = ((Long) request
1610: .getAttribute("org.apache.tomcat.sendfile.start"))
1611: .longValue();
1612: sendfileData.length = ((Long) request
1613: .getAttribute("org.apache.tomcat.sendfile.end"))
1614: .longValue()
1615: - sendfileData.pos;
1616: }
1617: }
1618:
1619: // Check for compression
1620: boolean useCompression = false;
1621: if (entityBody && (compressionLevel > 0)
1622: && (sendfileData == null)) {
1623: useCompression = isCompressable();
1624: // Change content-length to -1 to force chunking
1625: if (useCompression) {
1626: response.setContentLength(-1);
1627: }
1628: }
1629:
1630: MimeHeaders headers = response.getMimeHeaders();
1631: if (!entityBody) {
1632: response.setContentLength(-1);
1633: } else {
1634: String contentType = response.getContentType();
1635: if (contentType != null) {
1636: headers.setValue("Content-Type").setString(contentType);
1637: }
1638: String contentLanguage = response.getContentLanguage();
1639: if (contentLanguage != null) {
1640: headers.setValue("Content-Language").setString(
1641: contentLanguage);
1642: }
1643: }
1644:
1645: long contentLength = response.getContentLengthLong();
1646: if (contentLength != -1) {
1647: headers.setValue("Content-Length").setLong(contentLength);
1648: outputBuffer
1649: .addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
1650: contentDelimitation = true;
1651: } else {
1652: if (entityBody && http11 && keepAlive) {
1653: outputBuffer
1654: .addActiveFilter(outputFilters[Constants.CHUNKED_FILTER]);
1655: contentDelimitation = true;
1656: headers.addValue(Constants.TRANSFERENCODING).setString(
1657: Constants.CHUNKED);
1658: } else {
1659: outputBuffer
1660: .addActiveFilter(outputFilters[Constants.IDENTITY_FILTER]);
1661: }
1662: }
1663:
1664: if (useCompression) {
1665: outputBuffer
1666: .addActiveFilter(outputFilters[Constants.GZIP_FILTER]);
1667: headers.setValue("Content-Encoding").setString("gzip");
1668: // Make Proxies happy via Vary (from mod_deflate)
1669: headers.setValue("Vary").setString("Accept-Encoding");
1670: }
1671:
1672: // Add date header
1673: headers.setValue("Date").setString(
1674: FastHttpDateFormat.getCurrentDate());
1675:
1676: // FIXME: Add transfer encoding header
1677:
1678: if ((entityBody) && (!contentDelimitation)) {
1679: // Mark as close the connection after the request, and add the
1680: // connection: close header
1681: keepAlive = false;
1682: }
1683:
1684: // If we know that the request is bad this early, add the
1685: // Connection: close header.
1686: keepAlive = keepAlive && !statusDropsConnection(statusCode);
1687: if (!keepAlive) {
1688: headers.addValue(Constants.CONNECTION).setString(
1689: Constants.CLOSE);
1690: } else if (!http11 && !error) {
1691: headers.addValue(Constants.CONNECTION).setString(
1692: Constants.KEEPALIVE);
1693: }
1694:
1695: // Build the response header
1696: outputBuffer.sendStatus();
1697:
1698: // Add server header
1699: if (server != null) {
1700: headers.setValue("Server").setString(server);
1701: } else {
1702: outputBuffer.write(Constants.SERVER_BYTES);
1703: }
1704:
1705: int size = headers.size();
1706: for (int i = 0; i < size; i++) {
1707: outputBuffer.sendHeader(headers.getName(i), headers
1708: .getValue(i));
1709: }
1710: outputBuffer.endHeaders();
1711:
1712: }
1713:
1714: /**
1715: * Initialize standard input and output filters.
1716: */
1717: protected void initializeFilters() {
1718:
1719: // Create and add the identity filters.
1720: inputBuffer.addFilter(new IdentityInputFilter());
1721: outputBuffer.addFilter(new IdentityOutputFilter());
1722:
1723: // Create and add the chunked filters.
1724: inputBuffer.addFilter(new ChunkedInputFilter());
1725: outputBuffer.addFilter(new ChunkedOutputFilter());
1726:
1727: // Create and add the void filters.
1728: inputBuffer.addFilter(new VoidInputFilter());
1729: outputBuffer.addFilter(new VoidOutputFilter());
1730:
1731: // Create and add buffered input filter
1732: inputBuffer.addFilter(new BufferedInputFilter());
1733:
1734: // Create and add the chunked filters.
1735: //inputBuffer.addFilter(new GzipInputFilter());
1736: outputBuffer.addFilter(new GzipOutputFilter());
1737:
1738: }
1739:
1740: /**
1741: * Add an input filter to the current request.
1742: *
1743: * @return false if the encoding was not found (which would mean it is
1744: * unsupported)
1745: */
1746: protected boolean addInputFilter(InputFilter[] inputFilters,
1747: String encodingName) {
1748: if (encodingName.equals("identity")) {
1749: // Skip
1750: } else if (encodingName.equals("chunked")) {
1751: inputBuffer
1752: .addActiveFilter(inputFilters[Constants.CHUNKED_FILTER]);
1753: contentDelimitation = true;
1754: } else {
1755: for (int i = 2; i < inputFilters.length; i++) {
1756: if (inputFilters[i].getEncodingName().toString()
1757: .equals(encodingName)) {
1758: inputBuffer.addActiveFilter(inputFilters[i]);
1759: return true;
1760: }
1761: }
1762: return false;
1763: }
1764: return true;
1765: }
1766:
1767: /**
1768: * Specialized utility method: find a sequence of lower case bytes inside
1769: * a ByteChunk.
1770: */
1771: protected int findBytes(ByteChunk bc, byte[] b) {
1772:
1773: byte first = b[0];
1774: byte[] buff = bc.getBuffer();
1775: int start = bc.getStart();
1776: int end = bc.getEnd();
1777:
1778: // Look for first char
1779: int srcEnd = b.length;
1780:
1781: for (int i = start; i <= (end - srcEnd); i++) {
1782: if (Ascii.toLower(buff[i]) != first)
1783: continue;
1784: // found first char, now look for a match
1785: int myPos = i + 1;
1786: for (int srcPos = 1; srcPos < srcEnd;) {
1787: if (Ascii.toLower(buff[myPos++]) != b[srcPos++])
1788: break;
1789: if (srcPos == srcEnd)
1790: return i - start; // found it
1791: }
1792: }
1793: return -1;
1794:
1795: }
1796:
1797: /**
1798: * Determine if we must drop the connection because of the HTTP status
1799: * code. Use the same list of codes as Apache/httpd.
1800: */
1801: protected boolean statusDropsConnection(int status) {
1802: return status == 400 /* SC_BAD_REQUEST */|| status == 408 /* SC_REQUEST_TIMEOUT */
1803: || status == 411 /* SC_LENGTH_REQUIRED */
1804: || status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */
1805: || status == 414 /* SC_REQUEST_URI_TOO_LARGE */
1806: || status == 500 /* SC_INTERNAL_SERVER_ERROR */
1807: || status == 503 /* SC_SERVICE_UNAVAILABLE */
1808: || status == 501 /* SC_NOT_IMPLEMENTED */;
1809: }
1810:
1811: }
|