}\r
\r
if (apr) {\r
- if ("HTTP/1.1".equals(protocol)) {\r
+ if ("HTTP/1.1/NIO".equals(protocol)) {\r
+ setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");\r
+ } else if ("HTTP/1.1".equals(protocol)) {\r
setProtocolHandlerClassName\r
("org.apache.coyote.http11.Http11AprProtocol");\r
} else if ("AJP/1.3".equals(protocol)) {\r
("org.apache.coyote.http11.Http11AprProtocol");\r
}\r
} else {\r
- if ("HTTP/1.1".equals(protocol)) {\r
+ if ("HTTP/1.1/NIO".equals(protocol)) {\r
+ setProtocolHandlerClassName("org.apache.coyote.http11.Http11NioProtocol");\r
+ }else if ("HTTP/1.1".equals(protocol)) {\r
setProtocolHandlerClassName\r
("org.apache.coyote.http11.Http11Protocol");\r
} else if ("AJP/1.3".equals(protocol)) {\r
--- /dev/null
+/*\r
+ * Copyright 1999-2004 The Apache Software Foundation\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.coyote.http11;\r
+\r
+import java.io.IOException;\r
+import java.io.InterruptedIOException;\r
+import java.net.InetAddress;\r
+import java.nio.channels.SocketChannel;\r
+import java.util.StringTokenizer;\r
+import java.util.regex.Pattern;\r
+import java.util.regex.PatternSyntaxException;\r
+\r
+import org.apache.coyote.ActionCode;\r
+import org.apache.coyote.ActionHook;\r
+import org.apache.coyote.Adapter;\r
+import org.apache.coyote.Request;\r
+import org.apache.coyote.RequestInfo;\r
+import org.apache.coyote.Response;\r
+import org.apache.coyote.http11.filters.BufferedInputFilter;\r
+import org.apache.coyote.http11.filters.ChunkedInputFilter;\r
+import org.apache.coyote.http11.filters.ChunkedOutputFilter;\r
+import org.apache.coyote.http11.filters.GzipOutputFilter;\r
+import org.apache.coyote.http11.filters.IdentityInputFilter;\r
+import org.apache.coyote.http11.filters.IdentityOutputFilter;\r
+import org.apache.coyote.http11.filters.SavedRequestInputFilter;\r
+import org.apache.coyote.http11.filters.VoidInputFilter;\r
+import org.apache.coyote.http11.filters.VoidOutputFilter;\r
+import org.apache.tomcat.util.buf.Ascii;\r
+import org.apache.tomcat.util.buf.ByteChunk;\r
+import org.apache.tomcat.util.buf.HexUtils;\r
+import org.apache.tomcat.util.buf.MessageBytes;\r
+import org.apache.tomcat.util.http.FastHttpDateFormat;\r
+import org.apache.tomcat.util.http.MimeHeaders;\r
+import org.apache.tomcat.util.net.NioEndpoint;\r
+import org.apache.tomcat.util.net.NioEndpoint.Handler;\r
+import org.apache.tomcat.util.net.NioEndpoint.Handler.SocketState;\r
+import org.apache.tomcat.util.net.NioEndpoint.SendfileData;\r
+import org.apache.tomcat.util.res.StringManager;\r
+import java.nio.channels.SelectionKey;\r
+\r
+\r
+/**\r
+ * Processes HTTP requests.\r
+ *\r
+ * @author Remy Maucherat\r
+ * @author Filip Hanik\r
+ */\r
+public class Http11NioProcessor implements ActionHook {\r
+\r
+\r
+ /**\r
+ * Logger.\r
+ */\r
+ protected static org.apache.commons.logging.Log log\r
+ = org.apache.commons.logging.LogFactory.getLog(Http11NioProcessor.class);\r
+\r
+ /**\r
+ * The string manager for this package.\r
+ */\r
+ protected static StringManager sm =\r
+ StringManager.getManager(Constants.Package);\r
+\r
+\r
+ // ----------------------------------------------------------- Constructors\r
+\r
+\r
+ public Http11NioProcessor(int headerBufferSize, NioEndpoint endpoint) {\r
+\r
+ this.endpoint = endpoint;\r
+\r
+ request = new Request();\r
+ int readTimeout = endpoint.getFirstReadTimeout();\r
+ if (readTimeout == 0) {\r
+ readTimeout = 100;\r
+ } else if (readTimeout < 0) {\r
+ readTimeout = timeout;\r
+ //readTimeout = -1;\r
+ }\r
+ inputBuffer = new InternalNioInputBuffer(request, headerBufferSize,\r
+ readTimeout);\r
+ request.setInputBuffer(inputBuffer);\r
+\r
+ response = new Response();\r
+ response.setHook(this);\r
+ outputBuffer = new InternalNioOutputBuffer(response, headerBufferSize);\r
+ response.setOutputBuffer(outputBuffer);\r
+ request.setResponse(response);\r
+\r
+ ssl = !"off".equalsIgnoreCase(endpoint.getSSLEngine());\r
+\r
+ initializeFilters();\r
+\r
+ // Cause loading of HexUtils\r
+ int foo = HexUtils.DEC[0];\r
+\r
+ // Cause loading of FastHttpDateFormat\r
+ FastHttpDateFormat.getCurrentDate();\r
+\r
+ }\r
+\r
+\r
+ // ----------------------------------------------------- Instance Variables\r
+\r
+\r
+ /**\r
+ * Associated adapter.\r
+ */\r
+ protected Adapter adapter = null;\r
+\r
+\r
+ /**\r
+ * Request object.\r
+ */\r
+ protected Request request = null;\r
+\r
+\r
+ /**\r
+ * Response object.\r
+ */\r
+ protected Response response = null;\r
+\r
+\r
+ /**\r
+ * Input.\r
+ */\r
+ protected InternalNioInputBuffer inputBuffer = null;\r
+\r
+\r
+ /**\r
+ * Output.\r
+ */\r
+ protected InternalNioOutputBuffer outputBuffer = null;\r
+\r
+\r
+ /**\r
+ * Error flag.\r
+ */\r
+ protected boolean error = false;\r
+\r
+\r
+ /**\r
+ * Keep-alive.\r
+ */\r
+ protected boolean keepAlive = true;\r
+\r
+\r
+ /**\r
+ * HTTP/1.1 flag.\r
+ */\r
+ protected boolean http11 = true;\r
+\r
+\r
+ /**\r
+ * HTTP/0.9 flag.\r
+ */\r
+ protected boolean http09 = false;\r
+\r
+\r
+ /**\r
+ * Sendfile data.\r
+ */\r
+ protected NioEndpoint.SendfileData sendfileData = null;\r
+\r
+\r
+ /**\r
+ * Comet used.\r
+ */\r
+ protected boolean comet = false;\r
+\r
+\r
+ /**\r
+ * Content delimitator for the request (if false, the connection will\r
+ * be closed at the end of the request).\r
+ */\r
+ protected boolean contentDelimitation = true;\r
+\r
+\r
+ /**\r
+ * Is there an expectation ?\r
+ */\r
+ protected boolean expectation = false;\r
+\r
+\r
+ /**\r
+ * List of restricted user agents.\r
+ */\r
+ protected Pattern[] restrictedUserAgents = null;\r
+\r
+\r
+ /**\r
+ * Maximum number of Keep-Alive requests to honor.\r
+ */\r
+ protected int maxKeepAliveRequests = -1;\r
+\r
+\r
+ /**\r
+ * SSL enabled ?\r
+ */\r
+ protected boolean ssl = false;\r
+\r
+\r
+ /**\r
+ * Socket associated with the current connection.\r
+ */\r
+ protected SocketChannel socket = null;\r
+\r
+\r
+ /**\r
+ * Remote Address associated with the current connection.\r
+ */\r
+ protected String remoteAddr = null;\r
+\r
+\r
+ /**\r
+ * Remote Host associated with the current connection.\r
+ */\r
+ protected String remoteHost = null;\r
+\r
+\r
+ /**\r
+ * Local Host associated with the current connection.\r
+ */\r
+ protected String localName = null;\r
+\r
+\r
+\r
+ /**\r
+ * Local port to which the socket is connected\r
+ */\r
+ protected int localPort = -1;\r
+\r
+\r
+ /**\r
+ * Remote port to which the socket is connected\r
+ */\r
+ protected int remotePort = -1;\r
+\r
+\r
+ /**\r
+ * The local Host address.\r
+ */\r
+ protected String localAddr = null;\r
+\r
+\r
+ /**\r
+ * Maximum timeout on uploads. 5 minutes as in Apache HTTPD server.\r
+ */\r
+ protected int timeout = 300000;\r
+\r
+\r
+ /**\r
+ * Flag to disable setting a different time-out on uploads.\r
+ */\r
+ protected boolean disableUploadTimeout = false;\r
+\r
+\r
+ /**\r
+ * Allowed compression level.\r
+ */\r
+ protected int compressionLevel = 0;\r
+\r
+\r
+ /**\r
+ * Minimum contentsize to make compression.\r
+ */\r
+ protected int compressionMinSize = 2048;\r
+\r
+\r
+ /**\r
+ * Socket buffering.\r
+ */\r
+ protected int socketBuffer = -1;\r
+\r
+\r
+ /**\r
+ * Max save post size.\r
+ */\r
+ protected int maxSavePostSize = 4 * 1024;\r
+\r
+\r
+ /**\r
+ * List of user agents to not use gzip with\r
+ */\r
+ protected Pattern noCompressionUserAgents[] = null;\r
+\r
+ /**\r
+ * List of MIMES which could be gzipped\r
+ */\r
+ protected String[] compressableMimeTypes =\r
+ { "text/html", "text/xml", "text/plain" };\r
+\r
+\r
+ /**\r
+ * Host name (used to avoid useless B2C conversion on the host name).\r
+ */\r
+ protected char[] hostNameC = new char[0];\r
+\r
+\r
+ /**\r
+ * Associated endpoint.\r
+ */\r
+ protected NioEndpoint endpoint;\r
+\r
+\r
+ /**\r
+ * Allow a customized the server header for the tin-foil hat folks.\r
+ */\r
+ protected String server = null;\r
+\r
+\r
+ // ------------------------------------------------------------- Properties\r
+\r
+\r
+ /**\r
+ * Return compression level.\r
+ */\r
+ public String getCompression() {\r
+ switch (compressionLevel) {\r
+ case 0:\r
+ return "off";\r
+ case 1:\r
+ return "on";\r
+ case 2:\r
+ return "force";\r
+ }\r
+ return "off";\r
+ }\r
+\r
+\r
+ /**\r
+ * Set compression level.\r
+ */\r
+ public void setCompression(String compression) {\r
+ if (compression.equals("on")) {\r
+ this.compressionLevel = 1;\r
+ } else if (compression.equals("force")) {\r
+ this.compressionLevel = 2;\r
+ } else if (compression.equals("off")) {\r
+ this.compressionLevel = 0;\r
+ } else {\r
+ try {\r
+ // Try to parse compression as an int, which would give the\r
+ // minimum compression size\r
+ compressionMinSize = Integer.parseInt(compression);\r
+ this.compressionLevel = 1;\r
+ } catch (Exception e) {\r
+ this.compressionLevel = 0;\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Set Minimum size to trigger compression.\r
+ */\r
+ public void setCompressionMinSize(int compressionMinSize) {\r
+ this.compressionMinSize = compressionMinSize;\r
+ }\r
+\r
+\r
+ /**\r
+ * Add user-agent for which gzip compression didn't works\r
+ * The user agent String given will be exactly matched\r
+ * to the user-agent header submitted by the client.\r
+ *\r
+ * @param userAgent user-agent string\r
+ */\r
+ public void addNoCompressionUserAgent(String userAgent) {\r
+ try {\r
+ Pattern nRule = Pattern.compile(userAgent);\r
+ noCompressionUserAgents =\r
+ addREArray(noCompressionUserAgents, nRule);\r
+ } catch (PatternSyntaxException pse) {\r
+ log.error(sm.getString("http11processor.regexp.error", userAgent), pse);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Set no compression user agent list (this method is best when used with\r
+ * a large number of connectors, where it would be better to have all of\r
+ * them referenced a single array).\r
+ */\r
+ public void setNoCompressionUserAgents(Pattern[] noCompressionUserAgents) {\r
+ this.noCompressionUserAgents = noCompressionUserAgents;\r
+ }\r
+\r
+\r
+ /**\r
+ * Set no compression user agent list.\r
+ * List contains users agents separated by ',' :\r
+ *\r
+ * ie: "gorilla,desesplorer,tigrus"\r
+ */\r
+ public void setNoCompressionUserAgents(String noCompressionUserAgents) {\r
+ if (noCompressionUserAgents != null) {\r
+ StringTokenizer st = new StringTokenizer(noCompressionUserAgents, ",");\r
+\r
+ while (st.hasMoreTokens()) {\r
+ addNoCompressionUserAgent(st.nextToken().trim());\r
+ }\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Add a mime-type which will be compressable\r
+ * The mime-type String will be exactly matched\r
+ * in the response mime-type header .\r
+ *\r
+ * @param mimeType mime-type string\r
+ */\r
+ public void addCompressableMimeType(String mimeType) {\r
+ compressableMimeTypes =\r
+ addStringArray(compressableMimeTypes, mimeType);\r
+ }\r
+\r
+\r
+ /**\r
+ * Set compressable mime-type list (this method is best when used with\r
+ * a large number of connectors, where it would be better to have all of\r
+ * them referenced a single array).\r
+ */\r
+ public void setCompressableMimeTypes(String[] compressableMimeTypes) {\r
+ this.compressableMimeTypes = compressableMimeTypes;\r
+ }\r
+\r
+\r
+ /**\r
+ * Set compressable mime-type list\r
+ * List contains users agents separated by ',' :\r
+ *\r
+ * ie: "text/html,text/xml,text/plain"\r
+ */\r
+ public void setCompressableMimeTypes(String compressableMimeTypes) {\r
+ if (compressableMimeTypes != null) {\r
+ StringTokenizer st = new StringTokenizer(compressableMimeTypes, ",");\r
+\r
+ while (st.hasMoreTokens()) {\r
+ addCompressableMimeType(st.nextToken().trim());\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Return the list of restricted user agents.\r
+ */\r
+ public String[] findCompressableMimeTypes() {\r
+ return (compressableMimeTypes);\r
+ }\r
+\r
+\r
+\r
+ // --------------------------------------------------------- Public Methods\r
+\r
+\r
+ /**\r
+ * Add input or output filter.\r
+ *\r
+ * @param className class name of the filter\r
+ */\r
+ protected void addFilter(String className) {\r
+ try {\r
+ Class clazz = Class.forName(className);\r
+ Object obj = clazz.newInstance();\r
+ if (obj instanceof InputFilter) {\r
+ inputBuffer.addFilter((InputFilter) obj);\r
+ } else if (obj instanceof OutputFilter) {\r
+ outputBuffer.addFilter((OutputFilter) obj);\r
+ } else {\r
+ log.warn(sm.getString("http11processor.filter.unknown", className));\r
+ }\r
+ } catch (Exception e) {\r
+ log.error(sm.getString("http11processor.filter.error", className), e);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * General use method\r
+ *\r
+ * @param sArray the StringArray\r
+ * @param value string\r
+ */\r
+ private String[] addStringArray(String sArray[], String value) {\r
+ String[] result = null;\r
+ if (sArray == null) {\r
+ result = new String[1];\r
+ result[0] = value;\r
+ }\r
+ else {\r
+ result = new String[sArray.length + 1];\r
+ for (int i = 0; i < sArray.length; i++)\r
+ result[i] = sArray[i];\r
+ result[sArray.length] = value;\r
+ }\r
+ return result;\r
+ }\r
+\r
+\r
+ /**\r
+ * General use method\r
+ *\r
+ * @param rArray the REArray\r
+ * @param value Obj\r
+ */\r
+ private Pattern[] addREArray(Pattern rArray[], Pattern value) {\r
+ Pattern[] result = null;\r
+ if (rArray == null) {\r
+ result = new Pattern[1];\r
+ result[0] = value;\r
+ }\r
+ else {\r
+ result = new Pattern[rArray.length + 1];\r
+ for (int i = 0; i < rArray.length; i++)\r
+ result[i] = rArray[i];\r
+ result[rArray.length] = value;\r
+ }\r
+ return result;\r
+ }\r
+\r
+\r
+ /**\r
+ * General use method\r
+ *\r
+ * @param sArray the StringArray\r
+ * @param value string\r
+ */\r
+ private boolean inStringArray(String sArray[], String value) {\r
+ for (int i = 0; i < sArray.length; i++) {\r
+ if (sArray[i].equals(value)) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+\r
+ /**\r
+ * Checks if any entry in the string array starts with the specified value\r
+ *\r
+ * @param sArray the StringArray\r
+ * @param value string\r
+ */\r
+ private boolean startsWithStringArray(String sArray[], String value) {\r
+ if (value == null)\r
+ return false;\r
+ for (int i = 0; i < sArray.length; i++) {\r
+ if (value.startsWith(sArray[i])) {\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+\r
+\r
+ /**\r
+ * Add restricted user-agent (which will downgrade the connector\r
+ * to HTTP/1.0 mode). The user agent String given will be matched\r
+ * via regexp to the user-agent header submitted by the client.\r
+ *\r
+ * @param userAgent user-agent string\r
+ */\r
+ public void addRestrictedUserAgent(String userAgent) {\r
+ try {\r
+ Pattern nRule = Pattern.compile(userAgent);\r
+ restrictedUserAgents = addREArray(restrictedUserAgents, nRule);\r
+ } catch (PatternSyntaxException pse) {\r
+ log.error(sm.getString("http11processor.regexp.error", userAgent), pse);\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Set restricted user agent list (this method is best when used with\r
+ * a large number of connectors, where it would be better to have all of\r
+ * them referenced a single array).\r
+ */\r
+ public void setRestrictedUserAgents(Pattern[] restrictedUserAgents) {\r
+ this.restrictedUserAgents = restrictedUserAgents;\r
+ }\r
+\r
+\r
+ /**\r
+ * Set restricted user agent list (which will downgrade the connector\r
+ * to HTTP/1.0 mode). List contains users agents separated by ',' :\r
+ *\r
+ * ie: "gorilla,desesplorer,tigrus"\r
+ */\r
+ public void setRestrictedUserAgents(String restrictedUserAgents) {\r
+ if (restrictedUserAgents != null) {\r
+ StringTokenizer st =\r
+ new StringTokenizer(restrictedUserAgents, ",");\r
+ while (st.hasMoreTokens()) {\r
+ addRestrictedUserAgent(st.nextToken().trim());\r
+ }\r
+ }\r
+ }\r
+\r
+\r
+ /**\r
+ * Return the list of restricted user agents.\r
+ */\r
+ public String[] findRestrictedUserAgents() {\r
+ String[] sarr = new String [restrictedUserAgents.length];\r
+\r
+ for (int i = 0; i < restrictedUserAgents.length; i++)\r
+ sarr[i] = restrictedUserAgents[i].toString();\r
+\r
+ return (sarr);\r
+ }\r
+\r
+\r
+ /**\r
+ * Set the maximum number of Keep-Alive requests to honor.\r
+ * This is to safeguard from DoS attacks. Setting to a negative\r
+ * value disables the check.\r
+ */\r
+ public void setMaxKeepAliveRequests(int mkar) {\r
+ maxKeepAliveRequests = mkar;\r
+ }\r
+\r
+\r
+ /**\r
+ * Return the number of Keep-Alive requests that we will honor.\r
+ */\r
+ public int getMaxKeepAliveRequests() {\r
+ return maxKeepAliveRequests;\r
+ }\r
+\r
+\r
+ /**\r
+ * Set the maximum size of a POST which will be buffered in SSL mode.\r
+ */\r
+ public void setMaxSavePostSize(int msps) {\r
+ maxSavePostSize = msps;\r
+ }\r
+\r
+\r
+ /**\r
+ * Return the maximum size of a POST which will be buffered in SSL mode.\r
+ */\r
+ public int getMaxSavePostSize() {\r
+ return maxSavePostSize;\r
+ }\r
+\r
+\r
+ /**\r
+ * Set the flag to control upload time-outs.\r
+ */\r
+ public void setDisableUploadTimeout(boolean isDisabled) {\r
+ disableUploadTimeout = isDisabled;\r
+ }\r
+\r
+ /**\r
+ * Get the flag that controls upload time-outs.\r
+ */\r
+ public boolean getDisableUploadTimeout() {\r
+ return disableUploadTimeout;\r
+ }\r
+\r
+ /**\r
+ * Set the socket buffer flag.\r
+ */\r
+ public void setSocketBuffer(int socketBuffer) {\r
+ this.socketBuffer = socketBuffer;\r
+ outputBuffer.setSocketBuffer(socketBuffer);\r
+ }\r
+\r
+ /**\r
+ * Get the socket buffer flag.\r
+ */\r
+ public int getSocketBuffer() {\r
+ return socketBuffer;\r
+ }\r
+\r
+ /**\r
+ * Set the upload timeout.\r
+ */\r
+ public void setTimeout( int timeouts ) {\r
+ timeout = timeouts ;\r
+ }\r
+\r
+ /**\r
+ * Get the upload timeout.\r
+ */\r
+ public int getTimeout() {\r
+ return timeout;\r
+ }\r
+\r
+\r
+ /**\r
+ * Set the server header name.\r
+ */\r
+ public void setServer( String server ) {\r
+ if (server==null || server.equals("")) {\r
+ this.server = null;\r
+ } else {\r
+ this.server = server;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Get the server header name.\r
+ */\r
+ public String getServer() {\r
+ return server;\r
+ }\r
+\r
+\r
+ /** Get the request associated with this processor.\r
+ *\r
+ * @return The request\r
+ */\r
+ public Request getRequest() {\r
+ return request;\r
+ }\r
+\r
+ /**\r
+ * Process pipelined HTTP requests using the specified input and output\r
+ * streams.\r
+ *\r
+ * @throws IOException error during an I/O operation\r
+ */\r
+ public SocketState event(boolean error)\r
+ throws IOException {\r
+\r
+ RequestInfo rp = request.getRequestProcessor();\r
+\r
+ try {\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);\r
+ error = !adapter.event(request, response, error);\r
+ if (request.getAttribute("org.apache.tomcat.comet") == null) {\r
+ comet = false;\r
+ }\r
+ SelectionKey key = socket.keyFor(endpoint.getPoller().getSelector());\r
+ if ( key != null ) {\r
+ NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();\r
+ if ( attach!=null ) attach.setComet(comet);\r
+ }\r
+ \r
+ } catch (InterruptedIOException e) {\r
+ error = true;\r
+ } catch (Throwable t) {\r
+ log.error(sm.getString("http11processor.request.process"), t);\r
+ // 500 - Internal Server Error\r
+ response.setStatus(500);\r
+ error = true;\r
+ }\r
+\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);\r
+\r
+ if (error) {\r
+ recycle();\r
+ return SocketState.CLOSED;\r
+ } else if (!comet) {\r
+ recycle();\r
+ endpoint.getPoller().add(socket);\r
+ return SocketState.OPEN;\r
+ } else {\r
+ endpoint.getCometPoller().add(socket);\r
+ return SocketState.LONG;\r
+ }\r
+ }\r
+\r
+ /**\r
+ * Process pipelined HTTP requests using the specified input and output\r
+ * streams.\r
+ *\r
+ * @throws IOException error during an I/O operation\r
+ */\r
+ public SocketState process(SocketChannel socket)\r
+ throws IOException {\r
+ RequestInfo rp = request.getRequestProcessor();\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);\r
+\r
+ // Set the remote address\r
+ remoteAddr = null;\r
+ remoteHost = null;\r
+ localAddr = null;\r
+ localName = null;\r
+ remotePort = -1;\r
+ localPort = -1;\r
+\r
+ // Setting up the socket\r
+ this.socket = socket;\r
+ inputBuffer.setSocket(socket);\r
+ outputBuffer.setSocket(socket);\r
+ outputBuffer.setSelector(endpoint.getPoller().getSelector());\r
+\r
+ // Error flag\r
+ error = false;\r
+ keepAlive = true;\r
+\r
+ int keepAliveLeft = maxKeepAliveRequests;\r
+ long soTimeout = endpoint.getSoTimeout();\r
+\r
+ int limit = 0;\r
+ if (endpoint.getFirstReadTimeout() > 0 || endpoint.getFirstReadTimeout() < -1) {\r
+ limit = endpoint.getMaxThreads() / 2;\r
+ }\r
+\r
+ boolean keptAlive = false;\r
+ boolean openSocket = false;\r
+\r
+ while (!error && keepAlive && !comet) {\r
+\r
+ // Parsing the request header\r
+ try {\r
+ if( !disableUploadTimeout && keptAlive && soTimeout > 0 ) {\r
+ socket.socket().setSoTimeout((int)soTimeout);\r
+ inputBuffer.readTimeout = soTimeout;\r
+ }\r
+ if (!inputBuffer.parseRequestLine\r
+ (keptAlive && (endpoint.getCurrentThreadsBusy() > limit))) {\r
+ // This means that no data is available right now\r
+ // (long keepalive), so that the processor should be recycled\r
+ // and the method should return true\r
+ openSocket = true;\r
+ // Add the socket to the poller\r
+ endpoint.getPoller().add(socket);\r
+ break;\r
+ }\r
+ request.setStartTime(System.currentTimeMillis());\r
+ keptAlive = true;\r
+ if (!disableUploadTimeout) {\r
+ socket.socket().setSoTimeout((int)timeout);\r
+ inputBuffer.readTimeout = soTimeout;\r
+ }\r
+ inputBuffer.parseHeaders();\r
+ } catch (IOException e) {\r
+ error = true;\r
+ break;\r
+ } catch (Throwable t) {\r
+ if (log.isDebugEnabled()) {\r
+ log.debug(sm.getString("http11processor.header.parse"), t);\r
+ }\r
+ // 400 - Bad Request\r
+ response.setStatus(400);\r
+ error = true;\r
+ }\r
+\r
+ // Setting up filters, and parse some request headers\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);\r
+ try {\r
+ prepareRequest();\r
+ } catch (Throwable t) {\r
+ if (log.isDebugEnabled()) {\r
+ log.debug(sm.getString("http11processor.request.prepare"), t);\r
+ }\r
+ // 400 - Internal Server Error\r
+ response.setStatus(400);\r
+ error = true;\r
+ }\r
+\r
+ if (maxKeepAliveRequests > 0 && --keepAliveLeft == 0)\r
+ keepAlive = false;\r
+\r
+ // Process the request in the adapter\r
+ if (!error) {\r
+ try {\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);\r
+ adapter.service(request, response);\r
+ // Handle when the response was committed before a serious\r
+ // error occurred. Throwing a ServletException should both\r
+ // set the status to 500 and set the errorException.\r
+ // If we fail here, then the response is likely already\r
+ // committed, so we can't try and set headers.\r
+ if(keepAlive && !error) { // Avoid checking twice.\r
+ error = response.getErrorException() != null ||\r
+ statusDropsConnection(response.getStatus());\r
+ }\r
+ // Comet support\r
+ if (request.getAttribute("org.apache.tomcat.comet") != null) {\r
+ comet = true;\r
+ }\r
+ SelectionKey key = socket.keyFor(endpoint.getPoller().getSelector());\r
+ if (key != null) {\r
+ NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();\r
+ if (attach != null) attach.setComet(comet);\r
+ }\r
+ } catch (InterruptedIOException e) {\r
+ error = true;\r
+ } catch (Throwable t) {\r
+ log.error(sm.getString("http11processor.request.process"), t);\r
+ // 500 - Internal Server Error\r
+ response.setStatus(500);\r
+ error = true;\r
+ }\r
+ }\r
+\r
+ // Finish the handling of the request\r
+ if (!comet) {\r
+ endRequest();\r
+ }\r
+\r
+ // If there was an error, make sure the request is counted as\r
+ // and error, and update the statistics counter\r
+ if (error) {\r
+ response.setStatus(500);\r
+ }\r
+ request.updateCounters();\r
+\r
+ // Do sendfile as needed: add socket to sendfile and end\r
+ if (sendfileData != null && !error) {\r
+ sendfileData.socket = socket;\r
+ sendfileData.keepAlive = keepAlive;\r
+ if (!endpoint.getSendfile().add(sendfileData)) {\r
+ openSocket = true;\r
+ break;\r
+ }\r
+ }\r
+\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);\r
+\r
+ }\r
+\r
+ rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);\r
+\r
+ if (comet) {\r
+ if (error) {\r
+ recycle();\r
+ return SocketState.CLOSED;\r
+ } else {\r
+ return SocketState.LONG;\r
+ }\r
+ } else {\r
+ recycle();\r
+ return (openSocket) ? SocketState.OPEN : SocketState.CLOSED;\r
+ }\r
+\r
+ }\r
+\r
+\r
+ public void endRequest() {\r
+\r
+ // Finish the handling of the request\r
+ try {\r
+ inputBuffer.endRequest();\r
+ } catch (IOException e) {\r
+ error = true;\r
+ } catch (Throwable t) {\r
+ log.error(sm.getString("http11processor.request.finish"), t);\r
+ // 500 - Internal Server Error\r
+ response.setStatus(500);\r
+ error = true;\r
+ }\r
+ try {\r
+ outputBuffer.endRequest();\r
+ } catch (IOException e) {\r
+ error = true;\r
+ } catch (Throwable t) {\r
+ log.error(sm.getString("http11processor.response.finish"), t);\r
+ error = true;\r
+ }\r
+\r
+ // Next request\r
+ inputBuffer.nextRequest();\r
+ outputBuffer.nextRequest();\r
+\r
+ }\r
+\r
+\r
+ public void recycle() {\r
+ inputBuffer.recycle();\r
+ outputBuffer.recycle();\r
+ this.socket = null;\r
+ }\r
+\r
+\r
+ // ----------------------------------------------------- ActionHook Methods\r
+\r
+\r
+ /**\r
+ * Send an action to the connector.\r
+ *\r
+ * @param actionCode Type of the action\r
+ * @param param Action parameter\r
+ */\r
+ public void action(ActionCode actionCode, Object param) {\r
+\r
+ if (actionCode == ActionCode.ACTION_COMMIT) {\r
+ // Commit current response\r
+\r
+ if (response.isCommitted())\r
+ return;\r
+\r
+ // Validate and write response headers\r
+ prepareResponse();\r
+ try {\r
+ outputBuffer.commit();\r
+ } catch (IOException e) {\r
+ // Set error flag\r
+ error = true;\r
+ }\r
+\r
+ } else if (actionCode == ActionCode.ACTION_ACK) {\r
+\r
+ // Acknowlege request\r
+\r
+ // Send a 100 status back if it makes sense (response not committed\r
+ // yet, and client specified an expectation for 100-continue)\r
+\r
+ if ((response.isCommitted()) || !expectation)\r
+ return;\r
+\r
+ inputBuffer.setSwallowInput(true);\r
+ try {\r
+ outputBuffer.sendAck();\r
+ } catch (IOException e) {\r
+ // Set error flag\r
+ error = true;\r
+ }\r
+\r
+ } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {\r
+\r
+ try {\r
+ outputBuffer.flush();\r
+ } catch (IOException e) {\r
+ // Set error flag\r
+ error = true;\r
+ response.setErrorException(e);\r
+ }\r
+\r
+ } else if (actionCode == ActionCode.ACTION_CLOSE) {\r
+ // Close\r
+\r
+ // End the processing of the current request, and stop any further\r
+ // transactions with the client\r
+\r
+ comet = false;\r
+ try {\r
+ outputBuffer.endRequest();\r
+ } catch (IOException e) {\r
+ // Set error flag\r
+ error = true;\r
+ }\r
+\r
+ } else if (actionCode == ActionCode.ACTION_RESET) {\r
+\r
+ // Reset response\r
+\r
+ // Note: This must be called before the response is committed\r
+\r
+ outputBuffer.reset();\r
+\r
+ } else if (actionCode == ActionCode.ACTION_CUSTOM) {\r
+\r
+ // Do nothing\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {\r
+\r
+ // Get remote host address\r
+ if ((remoteAddr == null) && (socket != null)) {\r
+ InetAddress inetAddr = socket.socket().getInetAddress();\r
+ if (inetAddr != null) {\r
+ remoteAddr = inetAddr.getHostAddress();\r
+ }\r
+ }\r
+ request.remoteAddr().setString(remoteAddr);\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {\r
+\r
+ // Get local host name\r
+ if ((localName == null) && (socket != null)) {\r
+ InetAddress inetAddr = socket.socket().getLocalAddress();\r
+ if (inetAddr != null) {\r
+ localName = inetAddr.getHostName();\r
+ }\r
+ }\r
+ request.localName().setString(localName);\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {\r
+\r
+ // Get remote host name\r
+ if ((remoteHost == null) && (socket != null)) {\r
+ InetAddress inetAddr = socket.socket().getInetAddress();\r
+ if (inetAddr != null) {\r
+ remoteHost = inetAddr.getHostName();\r
+ }\r
+ if(remoteHost == null) {\r
+ if(remoteAddr != null) {\r
+ remoteHost = remoteAddr;\r
+ } else { // all we can do is punt\r
+ request.remoteHost().recycle();\r
+ }\r
+ }\r
+ }\r
+ request.remoteHost().setString(remoteHost);\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {\r
+\r
+ if (localAddr == null)\r
+ localAddr = socket.socket().getLocalAddress().getHostAddress();\r
+\r
+ request.localAddr().setString(localAddr);\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {\r
+\r
+ if ((remotePort == -1 ) && (socket !=null)) {\r
+ remotePort = socket.socket().getPort();\r
+ }\r
+ request.setRemotePort(remotePort);\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {\r
+\r
+ if ((localPort == -1 ) && (socket !=null)) {\r
+ localPort = socket.socket().getLocalPort();\r
+ }\r
+ request.setLocalPort(localPort);\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {\r
+\r
+// if (ssl && (socket != 0)) {\r
+// try {\r
+// // Cipher suite\r
+// Object sslO = SSLSocket.getInfoS(socket, SSL.SSL_INFO_CIPHER);\r
+// if (sslO != null) {\r
+// request.setAttribute\r
+// (NioEndpoint.CIPHER_SUITE_KEY, sslO);\r
+// }\r
+// // Client certificate chain if present\r
+// int certLength = SSLSocket.getInfoI(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN);\r
+// X509Certificate[] certs = null;\r
+// if (certLength > 0) {\r
+// certs = new X509Certificate[certLength];\r
+// for (int i = 0; i < certLength; i++) {\r
+// byte[] data = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);\r
+// CertificateFactory cf =\r
+// CertificateFactory.getInstance("X.509");\r
+// ByteArrayInputStream stream = new ByteArrayInputStream(data);\r
+// certs[i] = (X509Certificate) cf.generateCertificate(stream);\r
+// }\r
+// }\r
+// if (certs != null) {\r
+// request.setAttribute\r
+// (NioEndpoint.CERTIFICATE_KEY, certs);\r
+// }\r
+// // User key size\r
+// sslO = new Integer(SSLSocket.getInfoI(socket, SSL.SSL_INFO_CIPHER_USEKEYSIZE));\r
+// if (sslO != null) {\r
+// request.setAttribute\r
+// (NioEndpoint.KEY_SIZE_KEY, sslO);\r
+// }\r
+// // SSL session ID\r
+// sslO = SSLSocket.getInfoS(socket, SSL.SSL_INFO_SESSION_ID);\r
+// if (sslO != null) {\r
+// request.setAttribute\r
+// (NioEndpoint.SESSION_ID_KEY, sslO);\r
+// }\r
+// } catch (Exception e) {\r
+// log.warn(sm.getString("http11processor.socket.ssl"), e);\r
+// }\r
+// }\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {\r
+\r
+// if (ssl && (socket != 0)) {\r
+// // Consume and buffer the request body, so that it does not\r
+// // interfere with the client's handshake messages\r
+// InputFilter[] inputFilters = inputBuffer.getFilters();\r
+// ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])\r
+// .setLimit(maxSavePostSize);\r
+// inputBuffer.addActiveFilter\r
+// (inputFilters[Constants.BUFFERED_FILTER]);\r
+// try {\r
+// // Renegociate certificates\r
+// SSLSocket.renegotiate(socket);\r
+// // Client certificate chain if present\r
+// int certLength = SSLSocket.getInfoI(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN);\r
+// X509Certificate[] certs = null;\r
+// if (certLength > 0) {\r
+// certs = new X509Certificate[certLength];\r
+// for (int i = 0; i < certLength; i++) {\r
+// byte[] data = SSLSocket.getInfoB(socket, SSL.SSL_INFO_CLIENT_CERT_CHAIN + i);\r
+// CertificateFactory cf =\r
+// CertificateFactory.getInstance("X.509");\r
+// ByteArrayInputStream stream = new ByteArrayInputStream(data);\r
+// certs[i] = (X509Certificate) cf.generateCertificate(stream);\r
+// }\r
+// }\r
+// if (certs != null) {\r
+// request.setAttribute\r
+// (NioEndpoint.CERTIFICATE_KEY, certs);\r
+// }\r
+// } catch (Exception e) {\r
+// log.warn(sm.getString("http11processor.socket.ssl"), e);\r
+// }\r
+// }\r
+\r
+ } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {\r
+ ByteChunk body = (ByteChunk) param;\r
+\r
+ InputFilter savedBody = new SavedRequestInputFilter(body);\r
+ savedBody.setRequest(request);\r
+\r
+ InternalNioInputBuffer internalBuffer = (InternalNioInputBuffer)\r
+ request.getInputBuffer();\r
+ internalBuffer.addActiveFilter(savedBody);\r
+ }\r
+\r
+ }\r
+\r
+\r
+ // ------------------------------------------------------ Connector Methods\r
+\r
+\r
+ /**\r
+ * Set the associated adapter.\r
+ *\r
+ * @param adapter the new adapter\r
+ */\r
+ public void setAdapter(Adapter adapter) {\r
+ this.adapter = adapter;\r
+ }\r
+\r
+\r
+ /**\r
+ * Get the associated adapter.\r
+ *\r
+ * @return the associated adapter\r
+ */\r
+ public Adapter getAdapter() {\r
+ return adapter;\r
+ }\r
+\r
+\r
+ // ------------------------------------------------------ Protected Methods\r
+\r
+\r
+ /**\r
+ * After reading the request headers, we have to setup the request filters.\r
+ */\r
+ protected void prepareRequest() {\r
+\r
+ http11 = true;\r
+ http09 = false;\r
+ contentDelimitation = false;\r
+ expectation = false;\r
+ sendfileData = null;\r
+ if (ssl) {\r
+ request.scheme().setString("https");\r
+ }\r
+ MessageBytes protocolMB = request.protocol();\r
+ if (protocolMB.equals(Constants.HTTP_11)) {\r
+ http11 = true;\r
+ protocolMB.setString(Constants.HTTP_11);\r
+ } else if (protocolMB.equals(Constants.HTTP_10)) {\r
+ http11 = false;\r
+ keepAlive = false;\r
+ protocolMB.setString(Constants.HTTP_10);\r
+ } else if (protocolMB.equals("")) {\r
+ // HTTP/0.9\r
+ http09 = true;\r
+ http11 = false;\r
+ keepAlive = false;\r
+ } else {\r
+ // Unsupported protocol\r
+ http11 = false;\r
+ error = true;\r
+ // Send 505; Unsupported HTTP version\r
+ response.setStatus(505);\r
+ }\r
+\r
+ MessageBytes methodMB = request.method();\r
+ if (methodMB.equals(Constants.GET)) {\r
+ methodMB.setString(Constants.GET);\r
+ } else if (methodMB.equals(Constants.POST)) {\r
+ methodMB.setString(Constants.POST);\r
+ }\r
+\r
+ MimeHeaders headers = request.getMimeHeaders();\r
+\r
+ // Check connection header\r
+ MessageBytes connectionValueMB = headers.getValue("connection");\r
+ if (connectionValueMB != null) {\r
+ ByteChunk connectionValueBC = connectionValueMB.getByteChunk();\r
+ if (findBytes(connectionValueBC, Constants.CLOSE_BYTES) != -1) {\r
+ keepAlive = false;\r
+ } else if (findBytes(connectionValueBC,\r
+ Constants.KEEPALIVE_BYTES) != -1) {\r
+ keepAlive = true;\r
+ }\r
+ }\r
+\r
+ MessageBytes expectMB = null;\r
+ if (http11)\r
+ expectMB = headers.getValue("expect");\r
+ if ((expectMB != null)\r
+ && (expectMB.indexOfIgnoreCase("100-continue", 0) != -1)) {\r
+ inputBuffer.setSwallowInput(false);\r
+ expectation = true;\r
+ }\r
+\r
+ // Check user-agent header\r
+ if ((restrictedUserAgents != null) && ((http11) || (keepAlive))) {\r
+ MessageBytes userAgentValueMB = headers.getValue("user-agent");\r
+ // Check in the restricted list, and adjust the http11\r
+ // and keepAlive flags accordingly\r
+ if(userAgentValueMB != null) {\r
+ String userAgentValue = userAgentValueMB.toString();\r
+ for (int i = 0; i < restrictedUserAgents.length; i++) {\r
+ if (restrictedUserAgents[i].matcher(userAgentValue).matches()) {\r
+ http11 = false;\r
+ keepAlive = false;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ // Check for a full URI (including protocol://host:port/)\r
+ ByteChunk uriBC = request.requestURI().getByteChunk();\r
+ if (uriBC.startsWithIgnoreCase("http", 0)) {\r
+\r
+ int pos = uriBC.indexOf("://", 0, 3, 4);\r
+ int uriBCStart = uriBC.getStart();\r
+ int slashPos = -1;\r
+ if (pos != -1) {\r
+ byte[] uriB = uriBC.getBytes();\r
+ slashPos = uriBC.indexOf('/', pos + 3);\r
+ if (slashPos == -1) {\r
+ slashPos = uriBC.getLength();\r
+ // Set URI as "/"\r
+ request.requestURI().setBytes\r
+ (uriB, uriBCStart + pos + 1, 1);\r
+ } else {\r
+ request.requestURI().setBytes\r
+ (uriB, uriBCStart + slashPos,\r
+ uriBC.getLength() - slashPos);\r
+ }\r
+ MessageBytes hostMB = headers.setValue("host");\r
+ hostMB.setBytes(uriB, uriBCStart + pos + 3,\r
+ slashPos - pos - 3);\r
+ }\r
+\r
+ }\r
+\r
+ // Input filter setup\r
+ InputFilter[] inputFilters = inputBuffer.getFilters();\r
+\r
+ // Parse transfer-encoding header\r
+ MessageBytes transferEncodingValueMB = null;\r
+ if (http11)\r
+ transferEncodingValueMB = headers.getValue("transfer-encoding");\r
+ if (transferEncodingValueMB != null) {\r
+ String transferEncodingValue = transferEncodingValueMB.toString();\r
+ // Parse the comma separated list. "identity" codings are ignored\r
+ int startPos = 0;\r
+ int commaPos = transferEncodingValue.indexOf(',');\r
+ String encodingName = null;\r
+ while (commaPos != -1) {\r
+ encodingName = transferEncodingValue.substring\r
+ (startPos, commaPos).toLowerCase().trim();\r
+ if (!addInputFilter(inputFilters, encodingName)) {\r
+ // Unsupported transfer encoding\r
+ error = true;\r
+ // 501 - Unimplemented\r
+ response.setStatus(501);\r
+ }\r
+ startPos = commaPos + 1;\r
+ commaPos = transferEncodingValue.indexOf(',', startPos);\r
+ }\r
+ encodingName = transferEncodingValue.substring(startPos)\r
+ .toLowerCase().trim();\r
+ if (!addInputFilter(inputFilters, encodingName)) {\r
+ // Unsupported transfer encoding\r
+ error = true;\r
+ // 501 - Unimplemented\r
+ response.setStatus(501);\r
+ }\r
+ }\r
+\r
+ // Parse content-length header\r
+ long contentLength = request.getContentLengthLong();\r
+ if (contentLength >= 0 && !contentDelimitation) {\r
+ inputBuffer.addActiveFilter\r
+ (inputFilters[Constants.IDENTITY_FILTER]);\r
+ contentDelimitation = true;\r
+ }\r
+\r
+ MessageBytes valueMB = headers.getValue("host");\r
+\r
+ // Check host header\r
+ if (http11 && (valueMB == null)) {\r
+ error = true;\r
+ // 400 - Bad request\r
+ response.setStatus(400);\r
+ }\r
+\r
+ parseHost(valueMB);\r
+\r
+ if (!contentDelimitation) {\r
+ // If there's no content length \r
+ // (broken HTTP/1.0 or HTTP/1.1), assume\r
+ // the client is not broken and didn't send a body\r
+ inputBuffer.addActiveFilter\r
+ (inputFilters[Constants.VOID_FILTER]);\r
+ contentDelimitation = true;\r
+ }\r
+\r
+ // Advertise sendfile support through a request attribute\r
+ if (endpoint.getUseSendfile()) {\r
+ request.setAttribute("org.apache.tomcat.sendfile.support", Boolean.FALSE);\r
+ }\r
+ // Advertise comet support through a request attribute\r
+ request.setAttribute("org.apache.tomcat.comet.support", Boolean.TRUE);\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Parse host.\r
+ */\r
+ public void parseHost(MessageBytes valueMB) {\r
+\r
+ if (valueMB == null || valueMB.isNull()) {\r
+ // HTTP/1.0\r
+ // Default is what the socket tells us. Overriden if a host is\r
+ // found/parsed\r
+ request.setServerPort(endpoint.getPort());\r
+ return;\r
+ }\r
+\r
+ ByteChunk valueBC = valueMB.getByteChunk();\r
+ byte[] valueB = valueBC.getBytes();\r
+ int valueL = valueBC.getLength();\r
+ int valueS = valueBC.getStart();\r
+ int colonPos = -1;\r
+ if (hostNameC.length < valueL) {\r
+ hostNameC = new char[valueL];\r
+ }\r
+\r
+ boolean ipv6 = (valueB[valueS] == '[');\r
+ boolean bracketClosed = false;\r
+ for (int i = 0; i < valueL; i++) {\r
+ char b = (char) valueB[i + valueS];\r
+ hostNameC[i] = b;\r
+ if (b == ']') {\r
+ bracketClosed = true;\r
+ } else if (b == ':') {\r
+ if (!ipv6 || bracketClosed) {\r
+ colonPos = i;\r
+ break;\r
+ }\r
+ }\r
+ }\r
+\r
+ if (colonPos < 0) {\r
+ if (!ssl) {\r
+ // 80 - Default HTTP port\r
+ request.setServerPort(80);\r
+ } else {\r
+ // 443 - Default HTTPS port\r
+ request.setServerPort(443);\r
+ }\r
+ request.serverName().setChars(hostNameC, 0, valueL);\r
+ } else {\r
+\r
+ request.serverName().setChars(hostNameC, 0, colonPos);\r
+\r
+ int port = 0;\r
+ int mult = 1;\r
+ for (int i = valueL - 1; i > colonPos; i--) {\r
+ int charValue = HexUtils.DEC[(int) valueB[i + valueS]];\r
+ if (charValue == -1) {\r
+ // Invalid character\r
+ error = true;\r
+ // 400 - Bad request\r
+ response.setStatus(400);\r
+ break;\r
+ }\r
+ port = port + (charValue * mult);\r
+ mult = 10 * mult;\r
+ }\r
+ request.setServerPort(port);\r
+\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Check for compression\r
+ */\r
+ private boolean isCompressable() {\r
+\r
+ // Nope Compression could works in HTTP 1.0 also\r
+ // cf: mod_deflate\r
+\r
+ // Compression only since HTTP 1.1\r
+ // if (! http11)\r
+ // return false;\r
+\r
+ // Check if browser support gzip encoding\r
+ MessageBytes acceptEncodingMB =\r
+ request.getMimeHeaders().getValue("accept-encoding");\r
+\r
+ if ((acceptEncodingMB == null)\r
+ || (acceptEncodingMB.indexOf("gzip") == -1))\r
+ return false;\r
+\r
+ // Check if content is not allready gzipped\r
+ MessageBytes contentEncodingMB =\r
+ response.getMimeHeaders().getValue("Content-Encoding");\r
+\r
+ if ((contentEncodingMB != null)\r
+ && (contentEncodingMB.indexOf("gzip") != -1))\r
+ return false;\r
+\r
+ // If force mode, allways compress (test purposes only)\r
+ if (compressionLevel == 2)\r
+ return true;\r
+\r
+ // Check for incompatible Browser\r
+ if (noCompressionUserAgents != null) {\r
+ MessageBytes userAgentValueMB =\r
+ request.getMimeHeaders().getValue("user-agent");\r
+ if(userAgentValueMB != null) {\r
+ String userAgentValue = userAgentValueMB.toString();\r
+\r
+ // If one Regexp rule match, disable compression\r
+ for (int i = 0; i < noCompressionUserAgents.length; i++)\r
+ if (noCompressionUserAgents[i].matcher(userAgentValue).matches())\r
+ return false;\r
+ }\r
+ }\r
+\r
+ // Check if suffisant len to trig the compression\r
+ long contentLength = response.getContentLengthLong();\r
+ if ((contentLength == -1)\r
+ || (contentLength > compressionMinSize)) {\r
+ // Check for compatible MIME-TYPE\r
+ if (compressableMimeTypes != null) {\r
+ return (startsWithStringArray(compressableMimeTypes,\r
+ response.getContentType()));\r
+ }\r
+ }\r
+\r
+ return false;\r
+ }\r
+\r
+\r
+ /**\r
+ * When committing the response, we have to validate the set of headers, as\r
+ * well as setup the response filters.\r
+ */\r
+ protected void prepareResponse() {\r
+\r
+ boolean entityBody = true;\r
+ contentDelimitation = false;\r
+\r
+ OutputFilter[] outputFilters = outputBuffer.getFilters();\r
+\r
+ if (http09 == true) {\r
+ // HTTP/0.9\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.IDENTITY_FILTER]);\r
+ return;\r
+ }\r
+\r
+ int statusCode = response.getStatus();\r
+ if ((statusCode == 204) || (statusCode == 205)\r
+ || (statusCode == 304)) {\r
+ // No entity body\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.VOID_FILTER]);\r
+ entityBody = false;\r
+ contentDelimitation = true;\r
+ }\r
+\r
+ MessageBytes methodMB = request.method();\r
+ if (methodMB.equals("HEAD")) {\r
+ // No entity body\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.VOID_FILTER]);\r
+ contentDelimitation = true;\r
+ }\r
+\r
+ // Sendfile support\r
+ if (endpoint.getUseSendfile()) {\r
+ String fileName = (String) request.getAttribute("org.apache.tomcat.sendfile.filename");\r
+ if (fileName != null) {\r
+ // No entity body sent here\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.VOID_FILTER]);\r
+ contentDelimitation = true;\r
+ sendfileData = new NioEndpoint.SendfileData();\r
+ sendfileData.fileName = fileName;\r
+ sendfileData.start = \r
+ ((Long) request.getAttribute("org.apache.tomcat.sendfile.start")).longValue();\r
+ sendfileData.end = \r
+ ((Long) request.getAttribute("org.apache.tomcat.sendfile.end")).longValue();\r
+ }\r
+ }\r
+\r
+ // Check for compression\r
+ boolean useCompression = false;\r
+ if (entityBody && (compressionLevel > 0) && (sendfileData == null)) {\r
+ useCompression = isCompressable();\r
+ // Change content-length to -1 to force chunking\r
+ if (useCompression) {\r
+ response.setContentLength(-1);\r
+ }\r
+ }\r
+\r
+ MimeHeaders headers = response.getMimeHeaders();\r
+ if (!entityBody) {\r
+ response.setContentLength(-1);\r
+ } else {\r
+ String contentType = response.getContentType();\r
+ if (contentType != null) {\r
+ headers.setValue("Content-Type").setString(contentType);\r
+ }\r
+ String contentLanguage = response.getContentLanguage();\r
+ if (contentLanguage != null) {\r
+ headers.setValue("Content-Language")\r
+ .setString(contentLanguage);\r
+ }\r
+ }\r
+\r
+ long contentLength = response.getContentLengthLong();\r
+ if (contentLength != -1) {\r
+ headers.setValue("Content-Length").setLong(contentLength);\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.IDENTITY_FILTER]);\r
+ contentDelimitation = true;\r
+ } else {\r
+ if (entityBody && http11 && keepAlive) {\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.CHUNKED_FILTER]);\r
+ contentDelimitation = true;\r
+ headers.addValue(Constants.TRANSFERENCODING).setString(Constants.CHUNKED);\r
+ } else {\r
+ outputBuffer.addActiveFilter\r
+ (outputFilters[Constants.IDENTITY_FILTER]);\r
+ }\r
+ }\r
+\r
+ if (useCompression) {\r
+ outputBuffer.addActiveFilter(outputFilters[Constants.GZIP_FILTER]);\r
+ headers.setValue("Content-Encoding").setString("gzip");\r
+ // Make Proxies happy via Vary (from mod_deflate)\r
+ headers.setValue("Vary").setString("Accept-Encoding");\r
+ }\r
+\r
+ // Add date header\r
+ headers.setValue("Date").setString(FastHttpDateFormat.getCurrentDate());\r
+\r
+ // FIXME: Add transfer encoding header\r
+\r
+ if ((entityBody) && (!contentDelimitation)) {\r
+ // Mark as close the connection after the request, and add the\r
+ // connection: close header\r
+ keepAlive = false;\r
+ }\r
+\r
+ // If we know that the request is bad this early, add the\r
+ // Connection: close header.\r
+ keepAlive = keepAlive && !statusDropsConnection(statusCode);\r
+ if (!keepAlive) {\r
+ headers.addValue(Constants.CONNECTION).setString(Constants.CLOSE);\r
+ } else if (!http11 && !error) {\r
+ headers.addValue(Constants.CONNECTION).setString(Constants.KEEPALIVE);\r
+ }\r
+\r
+ // Build the response header\r
+ outputBuffer.sendStatus();\r
+\r
+ // Add server header\r
+ if (server != null) {\r
+ headers.setValue("Server").setString(server);\r
+ } else {\r
+ outputBuffer.write(Constants.SERVER_BYTES);\r
+ }\r
+\r
+ int size = headers.size();\r
+ for (int i = 0; i < size; i++) {\r
+ outputBuffer.sendHeader(headers.getName(i), headers.getValue(i));\r
+ }\r
+ outputBuffer.endHeaders();\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Initialize standard input and output filters.\r
+ */\r
+ protected void initializeFilters() {\r
+\r
+ // Create and add the identity filters.\r
+ inputBuffer.addFilter(new IdentityInputFilter());\r
+ outputBuffer.addFilter(new IdentityOutputFilter());\r
+\r
+ // Create and add the chunked filters.\r
+ inputBuffer.addFilter(new ChunkedInputFilter());\r
+ outputBuffer.addFilter(new ChunkedOutputFilter());\r
+\r
+ // Create and add the void filters.\r
+ inputBuffer.addFilter(new VoidInputFilter());\r
+ outputBuffer.addFilter(new VoidOutputFilter());\r
+\r
+ // Create and add buffered input filter\r
+ inputBuffer.addFilter(new BufferedInputFilter());\r
+\r
+ // Create and add the chunked filters.\r
+ //inputBuffer.addFilter(new GzipInputFilter());\r
+ outputBuffer.addFilter(new GzipOutputFilter());\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Add an input filter to the current request.\r
+ *\r
+ * @return false if the encoding was not found (which would mean it is\r
+ * unsupported)\r
+ */\r
+ protected boolean addInputFilter(InputFilter[] inputFilters,\r
+ String encodingName) {\r
+ if (encodingName.equals("identity")) {\r
+ // Skip\r
+ } else if (encodingName.equals("chunked")) {\r
+ inputBuffer.addActiveFilter\r
+ (inputFilters[Constants.CHUNKED_FILTER]);\r
+ contentDelimitation = true;\r
+ } else {\r
+ for (int i = 2; i < inputFilters.length; i++) {\r
+ if (inputFilters[i].getEncodingName()\r
+ .toString().equals(encodingName)) {\r
+ inputBuffer.addActiveFilter(inputFilters[i]);\r
+ return true;\r
+ }\r
+ }\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+\r
+\r
+ /**\r
+ * Specialized utility method: find a sequence of lower case bytes inside\r
+ * a ByteChunk.\r
+ */\r
+ protected int findBytes(ByteChunk bc, byte[] b) {\r
+\r
+ byte first = b[0];\r
+ byte[] buff = bc.getBuffer();\r
+ int start = bc.getStart();\r
+ int end = bc.getEnd();\r
+\r
+ // Look for first char\r
+ int srcEnd = b.length;\r
+\r
+ for (int i = start; i <= (end - srcEnd); i++) {\r
+ if (Ascii.toLower(buff[i]) != first) continue;\r
+ // found first char, now look for a match\r
+ int myPos = i+1;\r
+ for (int srcPos = 1; srcPos < srcEnd; ) {\r
+ if (Ascii.toLower(buff[myPos++]) != b[srcPos++])\r
+ break;\r
+ if (srcPos == srcEnd) return i - start; // found it\r
+ }\r
+ }\r
+ return -1;\r
+\r
+ }\r
+\r
+ /**\r
+ * Determine if we must drop the connection because of the HTTP status\r
+ * code. Use the same list of codes as Apache/httpd.\r
+ */\r
+ protected boolean statusDropsConnection(int status) {\r
+ return status == 400 /* SC_BAD_REQUEST */ ||\r
+ status == 408 /* SC_REQUEST_TIMEOUT */ ||\r
+ status == 411 /* SC_LENGTH_REQUIRED */ ||\r
+ status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||\r
+ status == 414 /* SC_REQUEST_URI_TOO_LARGE */ ||\r
+ status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||\r
+ status == 503 /* SC_SERVICE_UNAVAILABLE */ ||\r
+ status == 501 /* SC_NOT_IMPLEMENTED */;\r
+ }\r
+\r
+}\r
--- /dev/null
+/*\r
+ * Copyright 1999-2004 The Apache Software Foundation\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.coyote.http11;\r
+\r
+import java.net.InetAddress;\r
+import java.net.URLEncoder;\r
+import java.nio.channels.SocketChannel;\r
+import java.util.Hashtable;\r
+import java.util.Iterator;\r
+import java.util.concurrent.ConcurrentHashMap;\r
+import java.util.concurrent.Executor;\r
+import javax.management.MBeanRegistration;\r
+import javax.management.MBeanServer;\r
+import javax.management.ObjectName;\r
+\r
+import org.apache.coyote.ActionCode;\r
+import org.apache.coyote.ActionHook;\r
+import org.apache.coyote.Adapter;\r
+import org.apache.coyote.ProtocolHandler;\r
+import org.apache.coyote.RequestGroupInfo;\r
+import org.apache.coyote.RequestInfo;\r
+import org.apache.tomcat.util.modeler.Registry;\r
+import org.apache.tomcat.util.net.NioEndpoint;\r
+import org.apache.tomcat.util.net.NioEndpoint.Handler;\r
+import org.apache.tomcat.util.res.StringManager;\r
+\r
+\r
+/**\r
+ * Abstract the protocol implementation, including threading, etc.\r
+ * Processor is single threaded and specific to stream-based protocols,\r
+ * will not fit Jk protocols like JNI.\r
+ *\r
+ * @author Remy Maucherat\r
+ * @author Costin Manolache\r
+ * @author Filip Hanik\r
+ */\r
+public class Http11NioProtocol implements ProtocolHandler, MBeanRegistration\r
+{\r
+ public Http11NioProtocol() {\r
+ cHandler = new Http11ConnectionHandler( this );\r
+ setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);\r
+ setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);\r
+ //setServerSoTimeout(Constants.DEFAULT_SERVER_SOCKET_TIMEOUT);\r
+ setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);\r
+ }\r
+\r
+ /**\r
+ * The string manager for this package.\r
+ */\r
+ protected static StringManager sm =\r
+ StringManager.getManager(Constants.Package);\r
+\r
+ /** Pass config info\r
+ */\r
+ public void setAttribute( String name, Object value ) {\r
+ if( log.isTraceEnabled())\r
+ log.trace(sm.getString("http11protocol.setattribute", name, value));\r
+\r
+ attributes.put(name, value);\r
+ }\r
+\r
+ public Object getAttribute( String key ) {\r
+ if( log.isTraceEnabled())\r
+ log.trace(sm.getString("http11protocol.getattribute", key));\r
+ return attributes.get(key);\r
+ }\r
+\r
+ public Iterator getAttributeNames() {\r
+ return attributes.keySet().iterator();\r
+ }\r
+\r
+ /**\r
+ * Set a property.\r
+ */\r
+ public void setProperty(String name, String value) {\r
+ setAttribute(name, value);\r
+ }\r
+\r
+ /**\r
+ * Get a property\r
+ */\r
+ public String getProperty(String name) {\r
+ return (String)getAttribute(name);\r
+ }\r
+\r
+ /** The adapter, used to call the connector\r
+ */\r
+ public void setAdapter(Adapter adapter) {\r
+ this.adapter=adapter;\r
+ }\r
+\r
+ public Adapter getAdapter() {\r
+ return adapter;\r
+ }\r
+\r
+\r
+ /** Start the protocol\r
+ */\r
+ public void init() throws Exception {\r
+ ep.setName(getName());\r
+ ep.setHandler(cHandler);\r
+\r
+ try {\r
+ ep.init();\r
+ } catch (Exception ex) {\r
+ log.error(sm.getString("http11protocol.endpoint.initerror"), ex);\r
+ throw ex;\r
+ }\r
+ if(log.isInfoEnabled())\r
+ log.info(sm.getString("http11protocol.init", getName()));\r
+\r
+ }\r
+\r
+ ObjectName tpOname;\r
+ ObjectName rgOname;\r
+\r
+ public void start() throws Exception {\r
+ if( this.domain != null ) {\r
+ try {\r
+ tpOname=new ObjectName\r
+ (domain + ":" + "type=ThreadPool,name=" + getName());\r
+ Registry.getRegistry(null, null)\r
+ .registerComponent(ep, tpOname, null );\r
+ } catch (Exception e) {\r
+ log.error("Can't register threadpool" );\r
+ }\r
+ rgOname=new ObjectName\r
+ (domain + ":type=GlobalRequestProcessor,name=" + getName());\r
+ Registry.getRegistry(null, null).registerComponent\r
+ ( cHandler.global, rgOname, null );\r
+ }\r
+\r
+ try {\r
+ ep.start();\r
+ } catch (Exception ex) {\r
+ log.error(sm.getString("http11protocol.endpoint.starterror"), ex);\r
+ throw ex;\r
+ }\r
+ if(log.isInfoEnabled())\r
+ log.info(sm.getString("http11protocol.start", getName()));\r
+ }\r
+\r
+ public void pause() throws Exception {\r
+ try {\r
+ ep.pause();\r
+ } catch (Exception ex) {\r
+ log.error(sm.getString("http11protocol.endpoint.pauseerror"), ex);\r
+ throw ex;\r
+ }\r
+ if(log.isInfoEnabled())\r
+ log.info(sm.getString("http11protocol.pause", getName()));\r
+ }\r
+\r
+ public void resume() throws Exception {\r
+ try {\r
+ ep.resume();\r
+ } catch (Exception ex) {\r
+ log.error(sm.getString("http11protocol.endpoint.resumeerror"), ex);\r
+ throw ex;\r
+ }\r
+ if(log.isInfoEnabled())\r
+ log.info(sm.getString("http11protocol.resume", getName()));\r
+ }\r
+\r
+ public void destroy() throws Exception {\r
+ if(log.isInfoEnabled())\r
+ log.info(sm.getString("http11protocol.stop", getName()));\r
+ ep.destroy();\r
+ if( tpOname!=null )\r
+ Registry.getRegistry(null, null).unregisterComponent(tpOname);\r
+ if( rgOname != null )\r
+ Registry.getRegistry(null, null).unregisterComponent(rgOname);\r
+ }\r
+\r
+ // -------------------- Properties--------------------\r
+ protected NioEndpoint ep=new NioEndpoint();\r
+ protected boolean secure;\r
+\r
+ protected Hashtable attributes = new Hashtable();\r
+\r
+ private int maxKeepAliveRequests=100; // as in Apache HTTPD server\r
+ private int timeout = 300000; // 5 minutes as in Apache HTTPD server\r
+ private int maxSavePostSize = 4 * 1024;\r
+ private int maxHttpHeaderSize = 8 * 1024;\r
+ private int socketCloseDelay=-1;\r
+ private boolean disableUploadTimeout = true;\r
+ private int socketBuffer = 9000;\r
+ private Adapter adapter;\r
+ private Http11ConnectionHandler cHandler;\r
+\r
+ /**\r
+ * Compression value.\r
+ */\r
+ private String compression = "off";\r
+ private String noCompressionUserAgents = null;\r
+ private String restrictedUserAgents = null;\r
+ private String compressableMimeTypes = "text/html,text/xml,text/plain";\r
+ private int compressionMinSize = 2048;\r
+\r
+ private String server;\r
+\r
+ // -------------------- Pool setup --------------------\r
+\r
+ // *\r
+ public Executor getExecutor() {\r
+ return ep.getExecutor();\r
+ }\r
+\r
+ // *\r
+ public void setExecutor(Executor executor) {\r
+ ep.setExecutor(executor);\r
+ }\r
+\r
+ public int getMaxThreads() {\r
+ return ep.getMaxThreads();\r
+ }\r
+\r
+ public void setMaxThreads( int maxThreads ) {\r
+ ep.setMaxThreads(maxThreads);\r
+ setAttribute("maxThreads", "" + maxThreads);\r
+ }\r
+\r
+ public void setThreadPriority(int threadPriority) {\r
+ ep.setThreadPriority(threadPriority);\r
+ setAttribute("threadPriority", "" + threadPriority);\r
+ }\r
+\r
+ public int getThreadPriority() {\r
+ return ep.getThreadPriority();\r
+ }\r
+\r
+ // -------------------- Tcp setup --------------------\r
+\r
+ public int getBacklog() {\r
+ return ep.getBacklog();\r
+ }\r
+\r
+ public void setBacklog( int i ) {\r
+ ep.setBacklog(i);\r
+ setAttribute("backlog", "" + i);\r
+ }\r
+\r
+ public int getPort() {\r
+ return ep.getPort();\r
+ }\r
+\r
+ public void setPort( int port ) {\r
+ ep.setPort(port);\r
+ setAttribute("port", "" + port);\r
+ }\r
+\r
+ public int getFirstReadTimeout() {\r
+ return ep.getFirstReadTimeout();\r
+ }\r
+\r
+ public void setFirstReadTimeout( int i ) {\r
+ ep.setFirstReadTimeout(i);\r
+ setAttribute("firstReadTimeout", "" + i);\r
+ }\r
+\r
+ public int getPollTime() {\r
+ return ep.getPollTime();\r
+ }\r
+\r
+ public void setPollTime( int i ) {\r
+ ep.setPollTime(i);\r
+ setAttribute("pollTime", "" + i);\r
+ }\r
+\r
+ public void setPollerSize(int i) {\r
+ ep.setPollerSize(i); \r
+ setAttribute("pollerSize", "" + i);\r
+ }\r
+\r
+ public int getPollerSize() {\r
+ return ep.getPollerSize();\r
+ }\r
+\r
+ public void setSendfileSize(int i) {\r
+ ep.setSendfileSize(i); \r
+ setAttribute("sendfileSize", "" + i);\r
+ }\r
+\r
+ public int getSendfileSize() {\r
+ return ep.getSendfileSize();\r
+ }\r
+\r
+ public boolean getUseSendfile() {\r
+ return ep.getUseSendfile();\r
+ }\r
+\r
+ public void setUseSendfile(boolean useSendfile) {\r
+ ep.setUseSendfile(useSendfile);\r
+ }\r
+\r
+ public InetAddress getAddress() {\r
+ return ep.getAddress();\r
+ }\r
+\r
+ public void setAddress(InetAddress ia) {\r
+ ep.setAddress( ia );\r
+ setAttribute("address", "" + ia);\r
+ }\r
+\r
+ public String getName() {\r
+ String encodedAddr = "";\r
+ if (getAddress() != null) {\r
+ encodedAddr = "" + getAddress();\r
+ if (encodedAddr.startsWith("/"))\r
+ encodedAddr = encodedAddr.substring(1);\r
+ encodedAddr = URLEncoder.encode(encodedAddr) + "-";\r
+ }\r
+ return ("http-" + encodedAddr + ep.getPort());\r
+ }\r
+\r
+ public boolean getTcpNoDelay() {\r
+ return ep.getTcpNoDelay();\r
+ }\r
+\r
+ public void setTcpNoDelay( boolean b ) {\r
+ ep.setTcpNoDelay( b );\r
+ setAttribute("tcpNoDelay", "" + b);\r
+ }\r
+\r
+ public boolean getDisableUploadTimeout() {\r
+ return disableUploadTimeout;\r
+ }\r
+\r
+ public void setDisableUploadTimeout(boolean isDisabled) {\r
+ disableUploadTimeout = isDisabled;\r
+ }\r
+\r
+ public int getSocketBuffer() {\r
+ return socketBuffer;\r
+ }\r
+\r
+ public void setSocketBuffer(int valueI) {\r
+ socketBuffer = valueI;\r
+ }\r
+\r
+ public String getCompression() {\r
+ return compression;\r
+ }\r
+\r
+ public void setCompression(String valueS) {\r
+ compression = valueS;\r
+ setAttribute("compression", valueS);\r
+ }\r
+\r
+ public int getMaxSavePostSize() {\r
+ return maxSavePostSize;\r
+ }\r
+\r
+ public void setMaxSavePostSize(int valueI) {\r
+ maxSavePostSize = valueI;\r
+ setAttribute("maxSavePostSize", "" + valueI);\r
+ }\r
+\r
+ public int getMaxHttpHeaderSize() {\r
+ return maxHttpHeaderSize;\r
+ }\r
+\r
+ public void setMaxHttpHeaderSize(int valueI) {\r
+ maxHttpHeaderSize = valueI;\r
+ setAttribute("maxHttpHeaderSize", "" + valueI);\r
+ }\r
+\r
+ public String getRestrictedUserAgents() {\r
+ return restrictedUserAgents;\r
+ }\r
+\r
+ public void setRestrictedUserAgents(String valueS) {\r
+ restrictedUserAgents = valueS;\r
+ setAttribute("restrictedUserAgents", valueS);\r
+ }\r
+\r
+ public String getNoCompressionUserAgents() {\r
+ return noCompressionUserAgents;\r
+ }\r
+\r
+ public void setNoCompressionUserAgents(String valueS) {\r
+ noCompressionUserAgents = valueS;\r
+ setAttribute("noCompressionUserAgents", valueS);\r
+ }\r
+\r
+ public String getCompressableMimeType() {\r
+ return compressableMimeTypes;\r
+ }\r
+\r
+ public void setCompressableMimeType(String valueS) {\r
+ compressableMimeTypes = valueS;\r
+ setAttribute("compressableMimeTypes", valueS);\r
+ }\r
+\r
+ public int getCompressionMinSize() {\r
+ return compressionMinSize;\r
+ }\r
+\r
+ public void setCompressionMinSize(int valueI) {\r
+ compressionMinSize = valueI;\r
+ setAttribute("compressionMinSize", "" + valueI);\r
+ }\r
+\r
+ public int getSoLinger() {\r
+ return ep.getSoLinger();\r
+ }\r
+\r
+ public void setSoLinger( int i ) {\r
+ ep.setSoLinger( i );\r
+ setAttribute("soLinger", "" + i);\r
+ }\r
+\r
+ public int getSoTimeout() {\r
+ return ep.getSoTimeout();\r
+ }\r
+\r
+ public void setSoTimeout( int i ) {\r
+ ep.setSoTimeout(i);\r
+ setAttribute("soTimeout", "" + i);\r
+ }\r
+\r
+ public String getProtocol() {\r
+ return getProperty("protocol");\r
+ }\r
+\r
+ public void setProtocol( String k ) {\r
+ setSecure(true);\r
+ setAttribute("protocol", k);\r
+ }\r
+\r
+ public boolean getSecure() {\r
+ return secure;\r
+ }\r
+\r
+ public void setSecure( boolean b ) {\r
+ secure=b;\r
+ setAttribute("secure", "" + b);\r
+ }\r
+\r
+ public int getMaxKeepAliveRequests() {\r
+ return maxKeepAliveRequests;\r
+ }\r
+\r
+ /** Set the maximum number of Keep-Alive requests that we will honor.\r
+ */\r
+ public void setMaxKeepAliveRequests(int mkar) {\r
+ maxKeepAliveRequests = mkar;\r
+ setAttribute("maxKeepAliveRequests", "" + mkar);\r
+ }\r
+\r
+ /**\r
+ * Return the Keep-Alive policy for the connection.\r
+ */\r
+ public boolean getKeepAlive() {\r
+ return ((maxKeepAliveRequests != 0) && (maxKeepAliveRequests != 1));\r
+ }\r
+\r
+ /**\r
+ * Set the keep-alive policy for this connection.\r
+ */\r
+ public void setKeepAlive(boolean keepAlive) {\r
+ if (!keepAlive) {\r
+ setMaxKeepAliveRequests(1);\r
+ }\r
+ }\r
+\r
+ public int getSocketCloseDelay() {\r
+ return socketCloseDelay;\r
+ }\r
+\r
+ public void setSocketCloseDelay( int d ) {\r
+ socketCloseDelay=d;\r
+ setAttribute("socketCloseDelay", "" + d);\r
+ }\r
+\r
+ public void setServer( String server ) {\r
+ this.server = server;\r
+ }\r
+\r
+ public String getServer() {\r
+ return server;\r
+ }\r
+\r
+ public int getTimeout() {\r
+ return timeout;\r
+ }\r
+\r
+ public void setTimeout( int timeouts ) {\r
+ timeout = timeouts;\r
+ setAttribute("timeout", "" + timeouts);\r
+ }\r
+\r
+ // -------------------- SSL related properties --------------------\r
+\r
+ /**\r
+ * SSL engine.\r
+ */\r
+ public String getSSLEngine() { return ep.getSSLEngine(); }\r
+ public void setSSLEngine(String SSLEngine) { ep.setSSLEngine(SSLEngine); }\r
+\r
+\r
+ /**\r
+ * SSL protocol.\r
+ */\r
+ public String getSSLProtocol() { return ep.getSSLProtocol(); }\r
+ public void setSSLProtocol(String SSLProtocol) { ep.setSSLProtocol(SSLProtocol); }\r
+\r
+\r
+ /**\r
+ * SSL password (if a cert is encrypted, and no password has been provided, a callback\r
+ * will ask for a password).\r
+ */\r
+ public String getSSLPassword() { return ep.getSSLPassword(); }\r
+ public void setSSLPassword(String SSLPassword) { ep.setSSLPassword(SSLPassword); }\r
+\r
+\r
+ /**\r
+ * SSL cipher suite.\r
+ */\r
+ public String getSSLCipherSuite() { return ep.getSSLCipherSuite(); }\r
+ public void setSSLCipherSuite(String SSLCipherSuite) { ep.setSSLCipherSuite(SSLCipherSuite); }\r
+\r
+\r
+ /**\r
+ * SSL certificate file.\r
+ */\r
+ public String getSSLCertificateFile() { return ep.getSSLCertificateFile(); }\r
+ public void setSSLCertificateFile(String SSLCertificateFile) { ep.setSSLCertificateFile(SSLCertificateFile); }\r
+\r
+\r
+ /**\r
+ * SSL certificate key file.\r
+ */\r
+ public String getSSLCertificateKeyFile() { return ep.getSSLCertificateKeyFile(); }\r
+ public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { ep.setSSLCertificateKeyFile(SSLCertificateKeyFile); }\r
+\r
+\r
+ /**\r
+ * SSL certificate chain file.\r
+ */\r
+ public String getSSLCertificateChainFile() { return ep.getSSLCertificateChainFile(); }\r
+ public void setSSLCertificateChainFile(String SSLCertificateChainFile) { ep.setSSLCertificateChainFile(SSLCertificateChainFile); }\r
+\r
+\r
+ /**\r
+ * SSL CA certificate path.\r
+ */\r
+ public String getSSLCACertificatePath() { return ep.getSSLCACertificatePath(); }\r
+ public void setSSLCACertificatePath(String SSLCACertificatePath) { ep.setSSLCACertificatePath(SSLCACertificatePath); }\r
+\r
+\r
+ /**\r
+ * SSL CA certificate file.\r
+ */\r
+ public String getSSLCACertificateFile() { return ep.getSSLCACertificateFile(); }\r
+ public void setSSLCACertificateFile(String SSLCACertificateFile) { ep.setSSLCACertificateFile(SSLCACertificateFile); }\r
+\r
+\r
+ /**\r
+ * SSL CA revocation path.\r
+ */\r
+ public String getSSLCARevocationPath() { return ep.getSSLCARevocationPath(); }\r
+ public void setSSLCARevocationPath(String SSLCARevocationPath) { ep.setSSLCARevocationPath(SSLCARevocationPath); }\r
+\r
+\r
+ /**\r
+ * SSL CA revocation file.\r
+ */\r
+ public String getSSLCARevocationFile() { return ep.getSSLCARevocationFile(); }\r
+ public void setSSLCARevocationFile(String SSLCARevocationFile) { ep.setSSLCARevocationFile(SSLCARevocationFile); }\r
+\r
+\r
+ /**\r
+ * SSL verify client.\r
+ */\r
+ public String getSSLVerifyClient() { return ep.getSSLVerifyClient(); }\r
+ public void setSSLVerifyClient(String SSLVerifyClient) { ep.setSSLVerifyClient(SSLVerifyClient); }\r
+\r
+\r
+ /**\r
+ * SSL verify depth.\r
+ */\r
+ public int getSSLVerifyDepth() { return ep.getSSLVerifyDepth(); }\r
+ public void setSSLVerifyDepth(int SSLVerifyDepth) { ep.setSSLVerifyDepth(SSLVerifyDepth); }\r
+\r
+ // -------------------- Connection handler --------------------\r
+\r
+ static class Http11ConnectionHandler implements Handler {\r
+\r
+ protected Http11NioProtocol proto;\r
+ protected static int count = 0;\r
+ protected RequestGroupInfo global = new RequestGroupInfo();\r
+\r
+ protected ThreadLocal<Http11NioProcessor> localProcessor = \r
+ new ThreadLocal<Http11NioProcessor>();\r
+ protected ConcurrentHashMap<SocketChannel, Http11NioProcessor> connections =\r
+ new ConcurrentHashMap<SocketChannel, Http11NioProcessor>();\r
+ protected java.util.Stack<Http11NioProcessor> recycledProcessors = \r
+ new java.util.Stack<Http11NioProcessor>();\r
+\r
+ Http11ConnectionHandler(Http11NioProtocol proto) {\r
+ this.proto = proto;\r
+ }\r
+\r
+ public SocketState event(SocketChannel socket, boolean error) {\r
+ Http11NioProcessor result = connections.get(socket);\r
+\r
+ SocketState state = SocketState.CLOSED; \r
+ if (result != null) {\r
+ boolean recycle = error;\r
+ // Call the appropriate event\r
+ try {\r
+ state = result.event(error);\r
+ } catch (java.net.SocketException e) {\r
+ // SocketExceptions are normal\r
+ Http11NioProtocol.log.debug\r
+ (sm.getString\r
+ ("http11protocol.proto.socketexception.debug"), e);\r
+ } catch (java.io.IOException e) {\r
+ // IOExceptions are normal\r
+ Http11NioProtocol.log.debug\r
+ (sm.getString\r
+ ("http11protocol.proto.ioexception.debug"), e);\r
+ }\r
+ // Future developers: if you discover any other\r
+ // rare-but-nonfatal exceptions, catch them here, and log as\r
+ // above.\r
+ catch (Throwable e) {\r
+ // any other exception or error is odd. Here we log it\r
+ // with "ERROR" level, so it will show up even on\r
+ // less-than-verbose logs.\r
+ Http11NioProtocol.log.error\r
+ (sm.getString("http11protocol.proto.error"), e);\r
+ } finally {\r
+ if (state != SocketState.LONG) {\r
+ connections.remove(socket);\r
+ recycledProcessors.push(result);\r
+ }\r
+ }\r
+ }\r
+ return state;\r
+ }\r
+\r
+ public SocketState process(SocketChannel socket) {\r
+ Http11NioProcessor processor = null;\r
+ try {\r
+ processor = (Http11NioProcessor) localProcessor.get();\r
+ if (processor == null) {\r
+ synchronized (recycledProcessors) {\r
+ if (!recycledProcessors.isEmpty()) {\r
+ processor = recycledProcessors.pop();\r
+ localProcessor.set(processor);\r
+ }\r
+ }\r
+ }\r
+ if (processor == null) {\r
+ processor =\r
+ new Http11NioProcessor(proto.maxHttpHeaderSize, proto.ep);\r
+ processor.setAdapter(proto.adapter);\r
+ processor.setMaxKeepAliveRequests(proto.maxKeepAliveRequests);\r
+ processor.setTimeout(proto.timeout);\r
+ processor.setDisableUploadTimeout(proto.disableUploadTimeout);\r
+ processor.setCompression(proto.compression);\r
+ processor.setCompressionMinSize(proto.compressionMinSize);\r
+ processor.setNoCompressionUserAgents(proto.noCompressionUserAgents);\r
+ processor.setCompressableMimeTypes(proto.compressableMimeTypes);\r
+ processor.setRestrictedUserAgents(proto.restrictedUserAgents);\r
+ processor.setSocketBuffer(proto.socketBuffer);\r
+ processor.setMaxSavePostSize(proto.maxSavePostSize);\r
+ processor.setServer(proto.server);\r
+ localProcessor.set(processor);\r
+ if (proto.getDomain() != null) {\r
+ synchronized (this) {\r
+ try {\r
+ RequestInfo rp = processor.getRequest().getRequestProcessor();\r
+ rp.setGlobalProcessor(global);\r
+ ObjectName rpName = new ObjectName\r
+ (proto.getDomain() + ":type=RequestProcessor,worker="\r
+ + proto.getName() + ",name=HttpRequest" + count++);\r
+ Registry.getRegistry(null, null).registerComponent(rp, rpName, null);\r
+ } catch (Exception e) {\r
+ log.warn("Error registering request");\r
+ }\r
+ }\r
+ }\r
+ }\r
+\r
+ if (processor instanceof ActionHook) {\r
+ ((ActionHook) processor).action(ActionCode.ACTION_START, null);\r
+ }\r
+\r
+ SocketState state = processor.process(socket);\r
+ if (state == SocketState.LONG) {\r
+ // Associate the connection with the processor. The next request \r
+ // processed by this thread will use either a new or a recycled\r
+ // processor.\r
+ connections.put(socket, processor);\r
+ localProcessor.set(null);\r
+ proto.ep.getCometPoller().add(socket);\r
+ }\r
+ return state;\r
+\r
+ } catch (java.net.SocketException e) {\r
+ // SocketExceptions are normal\r
+ Http11NioProtocol.log.debug\r
+ (sm.getString\r
+ ("http11protocol.proto.socketexception.debug"), e);\r
+ } catch (java.io.IOException e) {\r
+ // IOExceptions are normal\r
+ Http11NioProtocol.log.debug\r
+ (sm.getString\r
+ ("http11protocol.proto.ioexception.debug"), e);\r
+ }\r
+ // Future developers: if you discover any other\r
+ // rare-but-nonfatal exceptions, catch them here, and log as\r
+ // above.\r
+ catch (Throwable e) {\r
+ // any other exception or error is odd. Here we log it\r
+ // with "ERROR" level, so it will show up even on\r
+ // less-than-verbose logs.\r
+ Http11NioProtocol.log.error\r
+ (sm.getString("http11protocol.proto.error"), e);\r
+ }\r
+ return SocketState.CLOSED;\r
+ }\r
+ }\r
+\r
+ protected static org.apache.commons.logging.Log log\r
+ = org.apache.commons.logging.LogFactory.getLog(Http11NioProtocol.class);\r
+\r
+ // -------------------- Various implementation classes --------------------\r
+\r
+ protected String domain;\r
+ protected ObjectName oname;\r
+ protected MBeanServer mserver;\r
+\r
+ public ObjectName getObjectName() {\r
+ return oname;\r
+ }\r
+\r
+ public String getDomain() {\r
+ return domain;\r
+ }\r
+\r
+ public ObjectName preRegister(MBeanServer server,\r
+ ObjectName name) throws Exception {\r
+ oname=name;\r
+ mserver=server;\r
+ domain=name.getDomain();\r
+ return name;\r
+ }\r
+\r
+ public void postRegister(Boolean registrationDone) {\r
+ }\r
+\r
+ public void preDeregister() throws Exception {\r
+ }\r
+\r
+ public void postDeregister() {\r
+ }\r
+}\r
--- /dev/null
+/*\r
+ * Copyright 1999-2004 The Apache Software Foundation\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+\r
+package org.apache.coyote.http11;\r
+\r
+import java.io.EOFException;\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.nio.channels.SocketChannel;\r
+\r
+import org.apache.coyote.InputBuffer;\r
+import org.apache.coyote.Request;\r
+import org.apache.tomcat.util.buf.ByteChunk;\r
+import org.apache.tomcat.util.buf.MessageBytes;\r
+import org.apache.tomcat.util.http.MimeHeaders;\r
+import org.apache.tomcat.util.res.StringManager;\r
+\r
+/**\r
+ * Implementation of InputBuffer which provides HTTP request header parsing as\r
+ * well as transfer decoding.\r
+ *\r
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>\r
+ * @author Filip Hanik\r
+ */\r
+public class InternalNioInputBuffer implements InputBuffer {\r
+\r
+\r
+ // -------------------------------------------------------------- Constants\r
+\r
+\r
+ // ----------------------------------------------------------- Constructors\r
+\r
+\r
+ /**\r
+ * Alternate constructor.\r
+ */\r
+ public InternalNioInputBuffer(Request request, int headerBufferSize, \r
+ long readTimeout) {\r
+\r
+ this.request = request;\r
+ headers = request.getMimeHeaders();\r
+\r
+ buf = new byte[headerBufferSize];\r
+ if (headerBufferSize < (8 * 1024)) {\r
+ bbuf = ByteBuffer.allocateDirect(6 * 1500);\r
+ } else {\r
+ bbuf = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);\r
+ }\r
+\r
+ inputStreamInputBuffer = new SocketInputBuffer();\r
+\r
+ filterLibrary = new InputFilter[0];\r
+ activeFilters = new InputFilter[0];\r
+ lastActiveFilter = -1;\r
+\r
+ parsingHeader = true;\r
+ swallowInput = true;\r
+\r
+ if (readTimeout < 0) {\r
+ this.readTimeout = -1;\r
+ } else {\r
+ this.readTimeout = readTimeout;\r
+ }\r
+\r
+ }\r
+\r
+\r
+ // -------------------------------------------------------------- Variables\r
+\r
+\r
+ /**\r
+ * The string manager for this package.\r
+ */\r
+ protected static StringManager sm =\r
+ StringManager.getManager(Constants.Package);\r
+\r
+\r
+ // ----------------------------------------------------- Instance Variables\r
+\r
+\r
+ /**\r
+ * Associated Coyote request.\r
+ */\r
+ protected Request request;\r
+\r
+\r
+ /**\r
+ * Headers of the associated request.\r
+ */\r
+ protected MimeHeaders headers;\r
+\r
+\r
+ /**\r
+ * State.\r
+ */\r
+ protected boolean parsingHeader;\r
+\r
+\r
+ /**\r
+ * Swallow input ? (in the case of an expectation)\r
+ */\r
+ protected boolean swallowInput;\r
+\r
+\r
+ /**\r
+ * Pointer to the current read buffer.\r
+ */\r
+ protected byte[] buf;\r
+\r
+\r
+ /**\r
+ * Last valid byte.\r
+ */\r
+ protected int lastValid;\r
+\r
+\r
+ /**\r
+ * Position in the buffer.\r
+ */\r
+ protected int pos;\r
+\r
+\r
+ /**\r
+ * Pos of the end of the header in the buffer, which is also the\r
+ * start of the body.\r
+ */\r
+ protected int end;\r
+\r
+\r
+ /**\r
+ * Direct byte buffer used to perform actual reading.\r
+ */\r
+ protected ByteBuffer bbuf;\r
+\r
+\r
+ /**\r
+ * Underlying socket.\r
+ */\r
+ protected SocketChannel socket;\r
+\r
+\r
+ /**\r
+ * Underlying input buffer.\r
+ */\r
+ protected InputBuffer inputStreamInputBuffer;\r
+\r
+\r
+ /**\r
+ * Filter library.\r
+ * Note: Filter[0] is always the "chunked" filter.\r
+ */\r
+ protected InputFilter[] filterLibrary;\r
+\r
+\r
+ /**\r
+ * Active filters (in order).\r
+ */\r
+ protected InputFilter[] activeFilters;\r
+\r
+\r
+ /**\r
+ * Index of the last active filter.\r
+ */\r
+ protected int lastActiveFilter;\r
+\r
+\r
+ /**\r
+ * The socket timeout used when reading the first block of the request\r
+ * header.\r
+ */\r
+ protected long readTimeout;\r
+\r
+\r
+ // ------------------------------------------------------------- Properties\r
+\r
+\r
+ /**\r
+ * Set the underlying socket.\r
+ */\r
+ public void setSocket(SocketChannel socket) {\r
+ this.socket = socket;\r
+ }\r
+\r
+\r
+ /**\r
+ * Get the underlying socket input stream.\r
+ */\r
+ public SocketChannel getSocket() {\r
+ return socket;\r
+ }\r
+\r
+\r
+ /**\r
+ * Add an input filter to the filter library.\r
+ */\r
+ public void addFilter(InputFilter filter) {\r
+\r
+ InputFilter[] newFilterLibrary = \r
+ new InputFilter[filterLibrary.length + 1];\r
+ for (int i = 0; i < filterLibrary.length; i++) {\r
+ newFilterLibrary[i] = filterLibrary[i];\r
+ }\r
+ newFilterLibrary[filterLibrary.length] = filter;\r
+ filterLibrary = newFilterLibrary;\r
+\r
+ activeFilters = new InputFilter[filterLibrary.length];\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Get filters.\r
+ */\r
+ public InputFilter[] getFilters() {\r
+\r
+ return filterLibrary;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Clear filters.\r
+ */\r
+ public void clearFilters() {\r
+\r
+ filterLibrary = new InputFilter[0];\r
+ lastActiveFilter = -1;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Add an input filter to the filter library.\r
+ */\r
+ public void addActiveFilter(InputFilter filter) {\r
+\r
+ if (lastActiveFilter == -1) {\r
+ filter.setBuffer(inputStreamInputBuffer);\r
+ } else {\r
+ for (int i = 0; i <= lastActiveFilter; i++) {\r
+ if (activeFilters[i] == filter)\r
+ return;\r
+ }\r
+ filter.setBuffer(activeFilters[lastActiveFilter]);\r
+ }\r
+\r
+ activeFilters[++lastActiveFilter] = filter;\r
+\r
+ filter.setRequest(request);\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Set the swallow input flag.\r
+ */\r
+ public void setSwallowInput(boolean swallowInput) {\r
+ this.swallowInput = swallowInput;\r
+ }\r
+\r
+\r
+ // --------------------------------------------------------- Public Methods\r
+\r
+\r
+ /**\r
+ * Recycle the input buffer. This should be called when closing the \r
+ * connection.\r
+ */\r
+ public void recycle() {\r
+\r
+ // Recycle Request object\r
+ request.recycle();\r
+\r
+ socket = null;\r
+ lastValid = 0;\r
+ pos = 0;\r
+ lastActiveFilter = -1;\r
+ parsingHeader = true;\r
+ swallowInput = true;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * End processing of current HTTP request.\r
+ * Note: All bytes of the current request should have been already \r
+ * consumed. This method only resets all the pointers so that we are ready\r
+ * to parse the next HTTP request.\r
+ */\r
+ public void nextRequest() {\r
+\r
+ // Recycle Request object\r
+ request.recycle();\r
+\r
+ //System.out.println("LV-pos: " + (lastValid - pos));\r
+ // Copy leftover bytes to the beginning of the buffer\r
+ if (lastValid - pos > 0) {\r
+ int npos = 0;\r
+ int opos = pos;\r
+ while (lastValid - opos > opos - npos) {\r
+ System.arraycopy(buf, opos, buf, npos, opos - npos);\r
+ npos += pos;\r
+ opos += pos;\r
+ }\r
+ System.arraycopy(buf, opos, buf, npos, lastValid - opos);\r
+ }\r
+\r
+ // Recycle filters\r
+ for (int i = 0; i <= lastActiveFilter; i++) {\r
+ activeFilters[i].recycle();\r
+ }\r
+\r
+ // Reset pointers\r
+ lastValid = lastValid - pos;\r
+ pos = 0;\r
+ lastActiveFilter = -1;\r
+ parsingHeader = true;\r
+ swallowInput = true;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * End request (consumes leftover bytes).\r
+ * \r
+ * @throws IOException an undelying I/O error occured\r
+ */\r
+ public void endRequest()\r
+ throws IOException {\r
+\r
+ if (swallowInput && (lastActiveFilter != -1)) {\r
+ int extraBytes = (int) activeFilters[lastActiveFilter].end();\r
+ pos = pos - extraBytes;\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Read the request line. This function is meant to be used during the \r
+ * HTTP request header parsing. Do NOT attempt to read the request body \r
+ * using it.\r
+ *\r
+ * @throws IOException If an exception occurs during the underlying socket\r
+ * read operations, or if the given buffer is not big enough to accomodate\r
+ * the whole line.\r
+ * @return true if data is properly fed; false if no data is available \r
+ * immediately and thread should be freed\r
+ */\r
+ public boolean parseRequestLine(boolean useAvailableData)\r
+ throws IOException {\r
+\r
+ int start = 0;\r
+\r
+ //\r
+ // Skipping blank lines\r
+ //\r
+\r
+ byte chr = 0;\r
+ do {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (useAvailableData) {\r
+ return false;\r
+ }\r
+ if (readTimeout == -1) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ } else {\r
+ // Do a simple read with a short timeout\r
+ if ( !readSocket(true) ) return false;\r
+ }\r
+ }\r
+\r
+ chr = buf[pos++];\r
+\r
+ } while ((chr == Constants.CR) || (chr == Constants.LF));\r
+\r
+ pos--;\r
+\r
+ // Mark the current buffer position\r
+ start = pos;\r
+\r
+ if (pos >= lastValid) {\r
+ if (useAvailableData) {\r
+ return false;\r
+ }\r
+ if (readTimeout == -1) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ } else {\r
+ // Do a simple read with a short timeout\r
+ if ( !readSocket(true) ) return false;\r
+ }\r
+ }\r
+\r
+ //\r
+ // Reading the method name\r
+ // Method name is always US-ASCII\r
+ //\r
+\r
+ boolean space = false;\r
+\r
+ while (!space) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ if (buf[pos] == Constants.SP) {\r
+ space = true;\r
+ request.method().setBytes(buf, start, pos - start);\r
+ }\r
+\r
+ pos++;\r
+\r
+ }\r
+\r
+ // Mark the current buffer position\r
+ start = pos;\r
+ int end = 0;\r
+ int questionPos = -1;\r
+\r
+ //\r
+ // Reading the URI\r
+ //\r
+\r
+ space = false;\r
+ boolean eol = false;\r
+\r
+ while (!space) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ if (buf[pos] == Constants.SP) {\r
+ space = true;\r
+ end = pos;\r
+ } else if ((buf[pos] == Constants.CR) \r
+ || (buf[pos] == Constants.LF)) {\r
+ // HTTP/0.9 style request\r
+ eol = true;\r
+ space = true;\r
+ end = pos;\r
+ } else if ((buf[pos] == Constants.QUESTION) \r
+ && (questionPos == -1)) {\r
+ questionPos = pos;\r
+ }\r
+\r
+ pos++;\r
+\r
+ }\r
+\r
+ request.unparsedURI().setBytes(buf, start, end - start);\r
+ if (questionPos >= 0) {\r
+ request.queryString().setBytes(buf, questionPos + 1, \r
+ end - questionPos - 1);\r
+ request.requestURI().setBytes(buf, start, questionPos - start);\r
+ } else {\r
+ request.requestURI().setBytes(buf, start, end - start);\r
+ }\r
+\r
+ // Mark the current buffer position\r
+ start = pos;\r
+ end = 0;\r
+\r
+ //\r
+ // Reading the protocol\r
+ // Protocol is always US-ASCII\r
+ //\r
+\r
+ while (!eol) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ if (buf[pos] == Constants.CR) {\r
+ end = pos;\r
+ } else if (buf[pos] == Constants.LF) {\r
+ if (end == 0)\r
+ end = pos;\r
+ eol = true;\r
+ }\r
+\r
+ pos++;\r
+\r
+ }\r
+\r
+ if ((end - start) > 0) {\r
+ request.protocol().setBytes(buf, start, end - start);\r
+ } else {\r
+ request.protocol().setString("");\r
+ }\r
+\r
+ return true;\r
+\r
+ }\r
+ \r
+ private void expand(int newsize) {\r
+ if ( newsize > buf.length ) {\r
+ byte[] tmp = new byte[newsize];\r
+ System.arraycopy(buf,0,tmp,0,buf.length);\r
+ buf = tmp;\r
+ tmp = null;\r
+ }\r
+ }\r
+ /**\r
+ * Perform blocking read with a timeout if desired\r
+ * @param timeout boolean - set to true if the system will time out\r
+ * @return boolean - true if data was read, false is EOF is reached\r
+ * @throws IOException\r
+ */\r
+ private boolean readSocket(boolean timeout) throws IOException {\r
+ int nRead = 0;\r
+ long start = System.currentTimeMillis();\r
+ boolean timedOut = false;\r
+ do {\r
+ bbuf.clear();\r
+ nRead = socket.read(bbuf);\r
+ if (nRead > 0) {\r
+ bbuf.flip();\r
+ bbuf.limit(nRead);\r
+ expand(nRead + pos);\r
+ bbuf.get(buf, pos, nRead);\r
+ lastValid = pos + nRead;\r
+ return true;\r
+ } else if (nRead == -1) {\r
+ return false;\r
+ }\r
+ timedOut = (readTimeout != -1) && ((System.currentTimeMillis()-start)>this.readTimeout);\r
+ if ( !timedOut && nRead == 0 ) try {Thread.sleep(25);}catch ( Exception x ) {}\r
+ }while ( nRead == 0 && (!timedOut) );\r
+ //else throw new IOException(sm.getString("iib.failedread"));\r
+ return false; //timeout\r
+ }\r
+\r
+\r
+ /**\r
+ * Parse the HTTP headers.\r
+ */\r
+ public void parseHeaders()\r
+ throws IOException {\r
+\r
+ while (parseHeader()) {\r
+ }\r
+\r
+ parsingHeader = false;\r
+ end = pos;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Parse an HTTP header.\r
+ * \r
+ * @return false after reading a blank line (which indicates that the\r
+ * HTTP header parsing is done\r
+ */\r
+ public boolean parseHeader()\r
+ throws IOException {\r
+\r
+ //\r
+ // Check for blank line\r
+ //\r
+\r
+ byte chr = 0;\r
+ while (true) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ chr = buf[pos];\r
+\r
+ if ((chr == Constants.CR) || (chr == Constants.LF)) {\r
+ if (chr == Constants.LF) {\r
+ pos++;\r
+ return false;\r
+ }\r
+ } else {\r
+ break;\r
+ }\r
+\r
+ pos++;\r
+\r
+ }\r
+\r
+ // Mark the current buffer position\r
+ int start = pos;\r
+\r
+ //\r
+ // Reading the header name\r
+ // Header name is always US-ASCII\r
+ //\r
+\r
+ boolean colon = false;\r
+ MessageBytes headerValue = null;\r
+\r
+ while (!colon) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ if (buf[pos] == Constants.COLON) {\r
+ colon = true;\r
+ headerValue = headers.addValue(buf, start, pos - start);\r
+ }\r
+ chr = buf[pos];\r
+ if ((chr >= Constants.A) && (chr <= Constants.Z)) {\r
+ buf[pos] = (byte) (chr - Constants.LC_OFFSET);\r
+ }\r
+\r
+ pos++;\r
+\r
+ }\r
+\r
+ // Mark the current buffer position\r
+ start = pos;\r
+ int realPos = pos;\r
+\r
+ //\r
+ // Reading the header value (which can be spanned over multiple lines)\r
+ //\r
+\r
+ boolean eol = false;\r
+ boolean validLine = true;\r
+\r
+ while (validLine) {\r
+\r
+ boolean space = true;\r
+\r
+ // Skipping spaces\r
+ while (space) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {\r
+ pos++;\r
+ } else {\r
+ space = false;\r
+ }\r
+\r
+ }\r
+\r
+ int lastSignificantChar = realPos;\r
+\r
+ // Reading bytes until the end of the line\r
+ while (!eol) {\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ if (buf[pos] == Constants.CR) {\r
+ } else if (buf[pos] == Constants.LF) {\r
+ eol = true;\r
+ } else if (buf[pos] == Constants.SP) {\r
+ buf[realPos] = buf[pos];\r
+ realPos++;\r
+ } else {\r
+ buf[realPos] = buf[pos];\r
+ realPos++;\r
+ lastSignificantChar = realPos;\r
+ }\r
+\r
+ pos++;\r
+\r
+ }\r
+\r
+ realPos = lastSignificantChar;\r
+\r
+ // Checking the first character of the new line. If the character\r
+ // is a LWS, then it's a multiline header\r
+\r
+ // Read new bytes if needed\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ throw new EOFException(sm.getString("iib.eof.error"));\r
+ }\r
+\r
+ chr = buf[pos];\r
+ if ((chr != Constants.SP) && (chr != Constants.HT)) {\r
+ validLine = false;\r
+ } else {\r
+ eol = false;\r
+ // Copying one extra space in the buffer (since there must\r
+ // be at least one space inserted between the lines)\r
+ buf[realPos] = chr;\r
+ realPos++;\r
+ }\r
+\r
+ }\r
+\r
+ // Set the header value\r
+ headerValue.setBytes(buf, start, realPos - start);\r
+\r
+ return true;\r
+\r
+ }\r
+\r
+\r
+ // ---------------------------------------------------- InputBuffer Methods\r
+\r
+\r
+ /**\r
+ * Read some bytes.\r
+ */\r
+ public int doRead(ByteChunk chunk, Request req) \r
+ throws IOException {\r
+\r
+ if (lastActiveFilter == -1)\r
+ return inputStreamInputBuffer.doRead(chunk, req);\r
+ else\r
+ return activeFilters[lastActiveFilter].doRead(chunk,req);\r
+\r
+ }\r
+\r
+\r
+ // ------------------------------------------------------ Protected Methods\r
+\r
+\r
+ /**\r
+ * Fill the internal buffer using data from the undelying input stream.\r
+ * \r
+ * @return false if at end of stream\r
+ */\r
+ protected boolean fill()\r
+ throws IOException {\r
+\r
+ boolean read = false;\r
+\r
+ if (parsingHeader) {\r
+\r
+ if (lastValid == buf.length) {\r
+ throw new IOException\r
+ (sm.getString("iib.requestheadertoolarge.error"));\r
+ }\r
+\r
+ // Do a simple read with a short timeout\r
+ read = readSocket(true);\r
+ } else {\r
+\r
+ if (buf.length - end < 4500) {\r
+ // In this case, the request header was really large, so we allocate a \r
+ // brand new one; the old one will get GCed when subsequent requests\r
+ // clear all references\r
+ buf = new byte[buf.length];\r
+ end = 0;\r
+ }\r
+ pos = end;\r
+ lastValid = pos;\r
+ // Do a simple read with a short timeout\r
+ read = readSocket(true);\r
+ }\r
+ return read;\r
+ }\r
+\r
+\r
+ // ------------------------------------- InputStreamInputBuffer Inner Class\r
+\r
+\r
+ /**\r
+ * This class is an input buffer which will read its data from an input\r
+ * stream.\r
+ */\r
+ protected class SocketInputBuffer \r
+ implements InputBuffer {\r
+\r
+\r
+ /**\r
+ * Read bytes into the specified chunk.\r
+ */\r
+ public int doRead(ByteChunk chunk, Request req ) \r
+ throws IOException {\r
+\r
+ if (pos >= lastValid) {\r
+ if (!fill())\r
+ return -1;\r
+ }\r
+\r
+ int length = lastValid - pos;\r
+ chunk.setBytes(buf, pos, length);\r
+ pos = lastValid;\r
+\r
+ return (length);\r
+\r
+ }\r
+\r
+\r
+ }\r
+\r
+\r
+}\r
--- /dev/null
+/*\r
+ * Copyright 1999-2004 The Apache Software Foundation\r
+ *\r
+ * Licensed under the Apache License, Version 2.0 (the "License");\r
+ * you may not use this file except in compliance with the License.\r
+ * You may obtain a copy of the License at\r
+ *\r
+ * http://www.apache.org/licenses/LICENSE-2.0\r
+ *\r
+ * Unless required by applicable law or agreed to in writing, software\r
+ * distributed under the License is distributed on an "AS IS" BASIS,\r
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+ * See the License for the specific language governing permissions and\r
+ * limitations under the License.\r
+ */\r
+\r
+package org.apache.coyote.http11;\r
+\r
+import java.io.IOException;\r
+import java.nio.ByteBuffer;\r
+import java.nio.channels.SocketChannel;\r
+\r
+import org.apache.coyote.ActionCode;\r
+import org.apache.coyote.OutputBuffer;\r
+import org.apache.coyote.Response;\r
+import org.apache.tomcat.util.buf.ByteChunk;\r
+import org.apache.tomcat.util.buf.CharChunk;\r
+import org.apache.tomcat.util.buf.MessageBytes;\r
+import org.apache.tomcat.util.http.HttpMessages;\r
+import org.apache.tomcat.util.http.MimeHeaders;\r
+import org.apache.tomcat.util.res.StringManager;\r
+import java.nio.channels.SelectionKey;\r
+import org.apache.tomcat.util.net.NioEndpoint;\r
+import java.nio.channels.Selector;\r
+\r
+/**\r
+ * Output buffer.\r
+ * \r
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>\r
+ * @author Filip Hanik\r
+ */\r
+public class InternalNioOutputBuffer \r
+ implements OutputBuffer {\r
+\r
+\r
+ // -------------------------------------------------------------- Constants\r
+\r
+\r
+ // ----------------------------------------------------------- Constructors\r
+ int bbufLimit = 0;\r
+ \r
+ Selector selector;\r
+\r
+ /**\r
+ * Default constructor.\r
+ */\r
+ public InternalNioOutputBuffer(Response response) {\r
+ this(response, Constants.DEFAULT_HTTP_HEADER_BUFFER_SIZE);\r
+ }\r
+\r
+\r
+ /**\r
+ * Alternate constructor.\r
+ */\r
+ public InternalNioOutputBuffer(Response response, int headerBufferSize) {\r
+\r
+ this.response = response;\r
+ headers = response.getMimeHeaders();\r
+\r
+ buf = new byte[headerBufferSize];\r
+ \r
+ if (headerBufferSize < (8 * 1024)) {\r
+ bbufLimit = 6 * 1500; \r
+ } else {\r
+ bbufLimit = (headerBufferSize / 1500 + 1) * 1500;\r
+ }\r
+ bbuf = ByteBuffer.allocateDirect(bbufLimit);\r
+\r
+ outputStreamOutputBuffer = new SocketOutputBuffer();\r
+\r
+ filterLibrary = new OutputFilter[0];\r
+ activeFilters = new OutputFilter[0];\r
+ lastActiveFilter = -1;\r
+\r
+ committed = false;\r
+ finished = false;\r
+\r
+ // Cause loading of HttpMessages\r
+ HttpMessages.getMessage(200);\r
+\r
+ }\r
+\r
+\r
+ // -------------------------------------------------------------- Variables\r
+\r
+\r
+ /**\r
+ * The string manager for this package.\r
+ */\r
+ protected static StringManager sm =\r
+ StringManager.getManager(Constants.Package);\r
+\r
+\r
+ // ----------------------------------------------------- Instance Variables\r
+\r
+\r
+ /**\r
+ * Associated Coyote response.\r
+ */\r
+ protected Response response;\r
+\r
+\r
+ /**\r
+ * Headers of the associated request.\r
+ */\r
+ protected MimeHeaders headers;\r
+\r
+\r
+ /**\r
+ * Committed flag.\r
+ */\r
+ protected boolean committed;\r
+\r
+\r
+ /**\r
+ * Finished flag.\r
+ */\r
+ protected boolean finished;\r
+\r
+\r
+ /**\r
+ * Pointer to the current write buffer.\r
+ */\r
+ protected byte[] buf;\r
+\r
+\r
+ /**\r
+ * Position in the buffer.\r
+ */\r
+ protected int pos;\r
+\r
+\r
+ /**\r
+ * Underlying socket.\r
+ */\r
+ protected SocketChannel socket;\r
+\r
+\r
+ /**\r
+ * Underlying output buffer.\r
+ */\r
+ protected OutputBuffer outputStreamOutputBuffer;\r
+\r
+\r
+ /**\r
+ * Filter library.\r
+ * Note: Filter[0] is always the "chunked" filter.\r
+ */\r
+ protected OutputFilter[] filterLibrary;\r
+\r
+\r
+ /**\r
+ * Active filter (which is actually the top of the pipeline).\r
+ */\r
+ protected OutputFilter[] activeFilters;\r
+\r
+\r
+ /**\r
+ * Index of the last active filter.\r
+ */\r
+ protected int lastActiveFilter;\r
+\r
+\r
+ /**\r
+ * Direct byte buffer used for writing.\r
+ */\r
+ protected ByteBuffer bbuf = null;\r
+\r
+\r
+ // ------------------------------------------------------------- Properties\r
+\r
+\r
+ /**\r
+ * Set the underlying socket.\r
+ */\r
+ public void setSocket(SocketChannel socket) {\r
+ this.socket = socket;\r
+ }\r
+\r
+ public void setSelector(Selector selector) {\r
+ this.selector = selector;\r
+ }\r
+\r
+ /**\r
+ * Get the underlying socket input stream.\r
+ */\r
+ public SocketChannel getSocket() {\r
+ return socket;\r
+ }\r
+ /**\r
+ * Set the socket buffer size.\r
+ */\r
+ public void setSocketBuffer(int socketBufferSize) {\r
+ // FIXME: Remove\r
+ }\r
+\r
+\r
+ /**\r
+ * Add an output filter to the filter library.\r
+ */\r
+ public void addFilter(OutputFilter filter) {\r
+\r
+ OutputFilter[] newFilterLibrary = \r
+ new OutputFilter[filterLibrary.length + 1];\r
+ for (int i = 0; i < filterLibrary.length; i++) {\r
+ newFilterLibrary[i] = filterLibrary[i];\r
+ }\r
+ newFilterLibrary[filterLibrary.length] = filter;\r
+ filterLibrary = newFilterLibrary;\r
+\r
+ activeFilters = new OutputFilter[filterLibrary.length];\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Get filters.\r
+ */\r
+ public OutputFilter[] getFilters() {\r
+\r
+ return filterLibrary;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Clear filters.\r
+ */\r
+ public void clearFilters() {\r
+\r
+ filterLibrary = new OutputFilter[0];\r
+ lastActiveFilter = -1;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Add an output filter to the filter library.\r
+ */\r
+ public void addActiveFilter(OutputFilter filter) {\r
+\r
+ if (lastActiveFilter == -1) {\r
+ filter.setBuffer(outputStreamOutputBuffer);\r
+ } else {\r
+ for (int i = 0; i <= lastActiveFilter; i++) {\r
+ if (activeFilters[i] == filter)\r
+ return;\r
+ }\r
+ filter.setBuffer(activeFilters[lastActiveFilter]);\r
+ }\r
+\r
+ activeFilters[++lastActiveFilter] = filter;\r
+\r
+ filter.setResponse(response);\r
+\r
+ }\r
+\r
+\r
+ // --------------------------------------------------------- Public Methods\r
+\r
+\r
+ /**\r
+ * Flush the response.\r
+ * \r
+ * @throws IOException an undelying I/O error occured\r
+ */\r
+ public void flush()\r
+ throws IOException {\r
+\r
+ if (!committed) {\r
+\r
+ // Send the connector a request for commit. The connector should\r
+ // then validate the headers, send them (using sendHeader) and \r
+ // set the filters accordingly.\r
+ response.action(ActionCode.ACTION_COMMIT, null);\r
+\r
+ }\r
+\r
+ // Flush the current buffer\r
+ flushBuffer();\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Reset current response.\r
+ * \r
+ * @throws IllegalStateException if the response has already been committed\r
+ */\r
+ public void reset() {\r
+\r
+ if (committed)\r
+ throw new IllegalStateException(/*FIXME:Put an error message*/);\r
+\r
+ // Recycle Request object\r
+ response.recycle();\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Recycle the output buffer. This should be called when closing the \r
+ * connection.\r
+ */\r
+ public void recycle() {\r
+\r
+ // Recycle Request object\r
+ response.recycle();\r
+ bbuf.clear();\r
+\r
+ socket = null;\r
+ pos = 0;\r
+ lastActiveFilter = -1;\r
+ committed = false;\r
+ finished = false;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * End processing of current HTTP request.\r
+ * Note: All bytes of the current request should have been already \r
+ * consumed. This method only resets all the pointers so that we are ready\r
+ * to parse the next HTTP request.\r
+ */\r
+ public void nextRequest() {\r
+\r
+ // Recycle Request object\r
+ response.recycle();\r
+\r
+ // Recycle filters\r
+ for (int i = 0; i <= lastActiveFilter; i++) {\r
+ activeFilters[i].recycle();\r
+ }\r
+\r
+ // Reset pointers\r
+ pos = 0;\r
+ lastActiveFilter = -1;\r
+ committed = false;\r
+ finished = false;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * End request.\r
+ * \r
+ * @throws IOException an undelying I/O error occured\r
+ */\r
+ public void endRequest()\r
+ throws IOException {\r
+\r
+ if (!committed) {\r
+\r
+ // Send the connector a request for commit. The connector should\r
+ // then validate the headers, send them (using sendHeader) and \r
+ // set the filters accordingly.\r
+ response.action(ActionCode.ACTION_COMMIT, null);\r
+\r
+ }\r
+\r
+ if (finished)\r
+ return;\r
+\r
+ if (lastActiveFilter != -1)\r
+ activeFilters[lastActiveFilter].end();\r
+\r
+ flushBuffer();\r
+\r
+ finished = true;\r
+\r
+ }\r
+\r
+\r
+ // ------------------------------------------------ HTTP/1.1 Output Methods\r
+\r
+\r
+ /**\r
+ * Send an acknoledgement.\r
+ */\r
+ public void sendAck()\r
+ throws IOException {\r
+\r
+ if (!committed) {\r
+ //Socket.send(socket, Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length) < 0\r
+ ByteBuffer buf = ByteBuffer.wrap(Constants.ACK_BYTES,0,Constants.ACK_BYTES.length); \r
+ writeToSocket(buf);\r
+ }\r
+\r
+ }\r
+\r
+ private void writeToSocket(ByteBuffer bytebuffer) throws IOException {\r
+ int limit = bytebuffer.position();\r
+ bytebuffer.rewind();\r
+ bytebuffer.limit(limit);\r
+ int remaining = limit;\r
+ while ( remaining > 0 ) {\r
+ int written = socket.write(bytebuffer);\r
+ remaining -= written;\r
+ }\r
+ bbuf.clear();\r
+ bbuf.rewind();\r
+ bbuf.limit(bbufLimit);\r
+\r
+ //System.out.println("Written:"+limit);\r
+ this.total = 0;\r
+ } \r
+\r
+\r
+ /**\r
+ * Send the response status line.\r
+ */\r
+ public void sendStatus() {\r
+\r
+ // Write protocol name\r
+ write(Constants.HTTP_11_BYTES);\r
+ buf[pos++] = Constants.SP;\r
+\r
+ // Write status code\r
+ int status = response.getStatus();\r
+ switch (status) {\r
+ case 200:\r
+ write(Constants._200_BYTES);\r
+ break;\r
+ case 400:\r
+ write(Constants._400_BYTES);\r
+ break;\r
+ case 404:\r
+ write(Constants._404_BYTES);\r
+ break;\r
+ default:\r
+ write(status);\r
+ }\r
+\r
+ buf[pos++] = Constants.SP;\r
+\r
+ // Write message\r
+ String message = response.getMessage();\r
+ if (message == null) {\r
+ write(HttpMessages.getMessage(status));\r
+ } else {\r
+ write(message);\r
+ }\r
+\r
+ // End the response status line\r
+ buf[pos++] = Constants.CR;\r
+ buf[pos++] = Constants.LF;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Send a header.\r
+ * \r
+ * @param name Header name\r
+ * @param value Header value\r
+ */\r
+ public void sendHeader(MessageBytes name, MessageBytes value) {\r
+\r
+ write(name);\r
+ buf[pos++] = Constants.COLON;\r
+ buf[pos++] = Constants.SP;\r
+ write(value);\r
+ buf[pos++] = Constants.CR;\r
+ buf[pos++] = Constants.LF;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Send a header.\r
+ * \r
+ * @param name Header name\r
+ * @param value Header value\r
+ */\r
+ public void sendHeader(ByteChunk name, ByteChunk value) {\r
+\r
+ write(name);\r
+ buf[pos++] = Constants.COLON;\r
+ buf[pos++] = Constants.SP;\r
+ write(value);\r
+ buf[pos++] = Constants.CR;\r
+ buf[pos++] = Constants.LF;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Send a header.\r
+ * \r
+ * @param name Header name\r
+ * @param value Header value\r
+ */\r
+ public void sendHeader(String name, String value) {\r
+\r
+ write(name);\r
+ buf[pos++] = Constants.COLON;\r
+ buf[pos++] = Constants.SP;\r
+ write(value);\r
+ buf[pos++] = Constants.CR;\r
+ buf[pos++] = Constants.LF;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * End the header block.\r
+ */\r
+ public void endHeaders() {\r
+\r
+ buf[pos++] = Constants.CR;\r
+ buf[pos++] = Constants.LF;\r
+\r
+ }\r
+\r
+\r
+ // --------------------------------------------------- OutputBuffer Methods\r
+\r
+\r
+ /**\r
+ * Write the contents of a byte chunk.\r
+ * \r
+ * @param chunk byte chunk\r
+ * @return number of bytes written\r
+ * @throws IOException an undelying I/O error occured\r
+ */\r
+ public int doWrite(ByteChunk chunk, Response res) \r
+ throws IOException {\r
+\r
+ if (!committed) {\r
+\r
+ // Send the connector a request for commit. The connector should\r
+ // then validate the headers, send them (using sendHeaders) and \r
+ // set the filters accordingly.\r
+ response.action(ActionCode.ACTION_COMMIT, null);\r
+\r
+ }\r
+\r
+ if (lastActiveFilter == -1)\r
+ return outputStreamOutputBuffer.doWrite(chunk, res);\r
+ else\r
+ return activeFilters[lastActiveFilter].doWrite(chunk, res);\r
+\r
+ }\r
+\r
+\r
+ // ------------------------------------------------------ Protected Methods\r
+\r
+\r
+ /**\r
+ * Commit the response.\r
+ * \r
+ * @throws IOException an undelying I/O error occured\r
+ */\r
+ protected void commit()\r
+ throws IOException {\r
+\r
+ // The response is now committed\r
+ committed = true;\r
+ response.setCommitted(true);\r
+\r
+ if (pos > 0) {\r
+ // Sending the response header buffer\r
+ addToBB(buf, 0, pos);\r
+ }\r
+\r
+ }\r
+\r
+ int total = 0;\r
+ private synchronized void addToBB(byte[] buf, int offset, int length) throws IOException {\r
+ try {\r
+ if (bbuf.capacity() <= (offset + length)) {\r
+ flushBuffer();\r
+ }\r
+ bbuf.put(buf, offset, length);\r
+ total += length;\r
+ }catch ( Exception x ) {\r
+ x.printStackTrace();\r
+ }\r
+ //System.out.println("Total:"+total);\r
+ }\r
+\r
+\r
+ /**\r
+ * This method will write the contents of the specyfied message bytes \r
+ * buffer to the output stream, without filtering. This method is meant to\r
+ * be used to write the response header.\r
+ * \r
+ * @param mb data to be written\r
+ */\r
+ protected void write(MessageBytes mb) {\r
+\r
+ if (mb.getType() == MessageBytes.T_BYTES) {\r
+ ByteChunk bc = mb.getByteChunk();\r
+ write(bc);\r
+ } else if (mb.getType() == MessageBytes.T_CHARS) {\r
+ CharChunk cc = mb.getCharChunk();\r
+ write(cc);\r
+ } else {\r
+ write(mb.toString());\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * This method will write the contents of the specyfied message bytes \r
+ * buffer to the output stream, without filtering. This method is meant to\r
+ * be used to write the response header.\r
+ * \r
+ * @param bc data to be written\r
+ */\r
+ protected void write(ByteChunk bc) {\r
+\r
+ // Writing the byte chunk to the output buffer\r
+ System.arraycopy(bc.getBytes(), bc.getStart(), buf, pos,\r
+ bc.getLength());\r
+ pos = pos + bc.getLength();\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * This method will write the contents of the specyfied char \r
+ * buffer to the output stream, without filtering. This method is meant to\r
+ * be used to write the response header.\r
+ * \r
+ * @param cc data to be written\r
+ */\r
+ protected void write(CharChunk cc) {\r
+\r
+ int start = cc.getStart();\r
+ int end = cc.getEnd();\r
+ char[] cbuf = cc.getBuffer();\r
+ for (int i = start; i < end; i++) {\r
+ char c = cbuf[i];\r
+ // Note: This is clearly incorrect for many strings,\r
+ // but is the only consistent approach within the current\r
+ // servlet framework. It must suffice until servlet output\r
+ // streams properly encode their output.\r
+ if ((c <= 31) && (c != 9)) {\r
+ c = ' ';\r
+ } else if (c == 127) {\r
+ c = ' ';\r
+ }\r
+ buf[pos++] = (byte) c;\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * This method will write the contents of the specyfied byte \r
+ * buffer to the output stream, without filtering. This method is meant to\r
+ * be used to write the response header.\r
+ * \r
+ * @param b data to be written\r
+ */\r
+ public void write(byte[] b) {\r
+\r
+ // Writing the byte chunk to the output buffer\r
+ System.arraycopy(b, 0, buf, pos, b.length);\r
+ pos = pos + b.length;\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * This method will write the contents of the specyfied String to the \r
+ * output stream, without filtering. This method is meant to be used to \r
+ * write the response header.\r
+ * \r
+ * @param s data to be written\r
+ */\r
+ protected void write(String s) {\r
+\r
+ if (s == null)\r
+ return;\r
+\r
+ // From the Tomcat 3.3 HTTP/1.0 connector\r
+ int len = s.length();\r
+ for (int i = 0; i < len; i++) {\r
+ char c = s.charAt (i);\r
+ // Note: This is clearly incorrect for many strings,\r
+ // but is the only consistent approach within the current\r
+ // servlet framework. It must suffice until servlet output\r
+ // streams properly encode their output.\r
+ if ((c <= 31) && (c != 9)) {\r
+ c = ' ';\r
+ } else if (c == 127) {\r
+ c = ' ';\r
+ }\r
+ buf[pos++] = (byte) c;\r
+ }\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * This method will print the specified integer to the output stream, \r
+ * without filtering. This method is meant to be used to write the \r
+ * response header.\r
+ * \r
+ * @param i data to be written\r
+ */\r
+ protected void write(int i) {\r
+\r
+ write(String.valueOf(i));\r
+\r
+ }\r
+\r
+\r
+ /**\r
+ * Callback to write data from the buffer.\r
+ */\r
+ protected void flushBuffer()\r
+ throws IOException {\r
+\r
+ //prevent timeout for async,\r
+ SelectionKey key = socket.keyFor(selector);\r
+ if (key != null) {\r
+ NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();\r
+ attach.access();\r
+ }\r
+\r
+ //write to the socket, if there is anything to write\r
+ if (bbuf.position() > 0) {\r
+ writeToSocket(bbuf);\r
+ }\r
+ }\r
+\r
+\r
+ // ----------------------------------- OutputStreamOutputBuffer Inner Class\r
+\r
+\r
+ /**\r
+ * This class is an output buffer which will write data to an output\r
+ * stream.\r
+ */\r
+ protected class SocketOutputBuffer \r
+ implements OutputBuffer {\r
+\r
+\r
+ /**\r
+ * Write chunk.\r
+ */\r
+ public int doWrite(ByteChunk chunk, Response res) \r
+ throws IOException {\r
+\r
+ int len = chunk.getLength();\r
+ int start = chunk.getStart();\r
+ byte[] b = chunk.getBuffer();\r
+ while (len > 0) {\r
+ int thisTime = len;\r
+ if (bbuf.position() == bbuf.capacity()) {\r
+ flushBuffer();\r
+ }\r
+ if (thisTime > bbuf.capacity() - bbuf.position()) {\r
+ thisTime = bbuf.capacity() - bbuf.position();\r
+ }\r
+ addToBB(b,start,thisTime);\r
+ len = len - thisTime;\r
+ start = start + thisTime;\r
+ }\r
+ return chunk.getLength();\r
+\r
+ }\r
+\r
+\r
+ }\r
+\r
+\r
+}\r
--- /dev/null
+/*
+ * Copyright 2005-2006 The Apache Software Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.apache.tomcat.util.net;
+
+import java.io.IOException;\r
+import java.net.InetAddress;\r
+import java.net.InetSocketAddress;\r
+import java.nio.channels.CancelledKeyException;\r
+import java.nio.channels.SelectionKey;\r
+import java.nio.channels.Selector;\r
+import java.nio.channels.ServerSocketChannel;\r
+import java.nio.channels.SocketChannel;\r
+import java.util.ArrayList;\r
+import java.util.HashMap;\r
+import java.util.Iterator;\r
+import java.util.LinkedList;\r
+import java.util.Set;\r
+import java.util.concurrent.Executor;\r
+\r
+import org.apache.commons.logging.Log;\r
+import org.apache.commons.logging.LogFactory;\r
+import org.apache.tomcat.jni.Error;\r
+import org.apache.tomcat.jni.Library;\r
+import org.apache.tomcat.jni.Poll;\r
+import org.apache.tomcat.jni.SSL;\r
+import org.apache.tomcat.jni.Status;\r
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * NIO tailored thread pool, providing the following services:
+ * <ul>
+ * <li>Socket acceptor thread</li>
+ * <li>Socket poller thread</li>
+ * <li>Sendfile thread</li>
+ * <li>Worker threads pool</li>
+ * </ul>
+ *
+ * When switching to Java 5, there's an opportunity to use the virtual
+ * machine's thread pool.
+ *
+ * @author Mladen Turk
+ * @author Remy Maucherat
+ * @author Filip Hanik
+ */
+public class NioEndpoint {
+
+
+ // -------------------------------------------------------------- Constants
+
+
+ protected static Log log = LogFactory.getLog(NioEndpoint.class);
+
+ protected static StringManager sm =
+ StringManager.getManager("org.apache.tomcat.util.net.res");
+
+
+ /**
+ * The Request attribute key for the cipher suite.
+ */
+ public static final String CIPHER_SUITE_KEY = "javax.servlet.request.cipher_suite";
+
+ /**
+ * The Request attribute key for the key size.
+ */
+ public static final String KEY_SIZE_KEY = "javax.servlet.request.key_size";
+
+ /**
+ * The Request attribute key for the client certificate chain.
+ */
+ public static final String CERTIFICATE_KEY = "javax.servlet.request.X509Certificate";
+
+ /**
+ * The Request attribute key for the session id.
+ * This one is a Tomcat extension to the Servlet spec.
+ */
+ public static final String SESSION_ID_KEY = "javax.servlet.request.ssl_session";
+
+
+ // ----------------------------------------------------------------- Fields
+
+
+ /**
+ * Available workers.
+ */
+ protected WorkerStack workers = null;
+
+
+ /**
+ * Running state of the endpoint.
+ */
+ protected volatile boolean running = false;
+
+
+ /**
+ * Will be set to true whenever the endpoint is paused.
+ */
+ protected volatile boolean paused = false;
+
+
+ /**
+ * Track the initialization state of the endpoint.
+ */
+ protected boolean initialized = false;
+
+
+ /**
+ * Current worker threads busy count.
+ */
+ protected int curThreadsBusy = 0;
+
+
+ /**
+ * Current worker threads count.
+ */
+ protected int curThreads = 0;
+
+
+ /**
+ * Sequence number used to generate thread names.
+ */
+ protected int sequence = 0;
+
+
+ /**
+ * Root APR memory pool.
+ */
+ protected long rootPool = 0;
+
+
+ /**
+ * Server socket "pointer".
+ */
+ protected ServerSocketChannel serverSock = null;
+
+
+ /**
+ * APR memory pool for the server socket.
+ */
+ protected long serverSockPool = 0;
+
+
+ /**
+ * SSL context.
+ */
+ protected long sslContext = 0;
+
+
+ // ------------------------------------------------------------- Properties
+
+
+ /**
+ * External Executor based thread pool.
+ */
+ protected Executor executor = null;
+ public void setExecutor(Executor executor) { this.executor = executor; }
+ public Executor getExecutor() { return executor; }
+
+
+ /**
+ * Maximum amount of worker threads.
+ */
+ protected int maxThreads = 40;
+ public void setMaxThreads(int maxThreads) { this.maxThreads = maxThreads; }
+ public int getMaxThreads() { return maxThreads; }
+
+
+ /**
+ * Priority of the acceptor and poller threads.
+ */
+ protected int threadPriority = Thread.NORM_PRIORITY;
+ public void setThreadPriority(int threadPriority) { this.threadPriority = threadPriority; }
+ public int getThreadPriority() { return threadPriority; }
+
+
+ /**
+ * Size of the socket poller.
+ */
+ protected int pollerSize = 8 * 1024;
+ public void setPollerSize(int pollerSize) { this.pollerSize = pollerSize; }
+ public int getPollerSize() { return pollerSize; }
+
+
+ /**
+ * Size of the sendfile (= concurrent files which can be served).
+ */
+ protected int sendfileSize = 1 * 1024;
+ public void setSendfileSize(int sendfileSize) { this.sendfileSize = sendfileSize; }
+ public int getSendfileSize() { return sendfileSize; }
+
+
+ /**
+ * Server socket port.
+ */
+ protected int port;
+ public int getPort() { return port; }
+ public void setPort(int port ) { this.port=port; }
+
+
+ /**
+ * Address for the server socket.
+ */
+ protected InetAddress address;
+ public InetAddress getAddress() { return address; }
+ public void setAddress(InetAddress address) { this.address = address; }
+
+
+ /**
+ * Handling of accepted sockets.
+ */
+ protected Handler handler = null;
+ public void setHandler(Handler handler ) { this.handler = handler; }
+ public Handler getHandler() { return handler; }
+
+
+ /**
+ * Allows the server developer to specify the backlog that
+ * should be used for server sockets. By default, this value
+ * is 100.
+ */
+ protected int backlog = 100;
+ public void setBacklog(int backlog) { if (backlog > 0) this.backlog = backlog; }
+ public int getBacklog() { return backlog; }
+
+
+ /**
+ * Socket TCP no delay.
+ */
+ protected boolean tcpNoDelay = false;
+ public boolean getTcpNoDelay() { return tcpNoDelay; }
+ public void setTcpNoDelay(boolean tcpNoDelay) { this.tcpNoDelay = tcpNoDelay; }
+
+
+ /**
+ * Socket linger.
+ */
+ protected int soLinger = 100;
+ public int getSoLinger() { return soLinger; }
+ public void setSoLinger(int soLinger) { this.soLinger = soLinger; }
+
+
+ /**
+ * Socket timeout.
+ */
+ protected int soTimeout = -1;
+ public int getSoTimeout() { return soTimeout; }
+ public void setSoTimeout(int soTimeout) { this.soTimeout = soTimeout; }
+
+
+ /**
+ * Timeout on first request read before going to the poller, in ms.
+ */
+ protected int firstReadTimeout = 60000;
+ public int getFirstReadTimeout() { return firstReadTimeout; }
+ public void setFirstReadTimeout(int firstReadTimeout) { this.firstReadTimeout = firstReadTimeout; }
+
+
+ /**
+ * Poll interval, in microseconds. The smaller the value, the more CPU the poller
+ * will use, but the more responsive to activity it will be.
+ */
+ protected int pollTime = 2000;
+ public int getPollTime() { return pollTime; }
+ public void setPollTime(int pollTime) { if (pollTime > 0) { this.pollTime = pollTime; } }
+
+
+ /**
+ * The default is true - the created threads will be
+ * in daemon mode. If set to false, the control thread
+ * will not be daemon - and will keep the process alive.
+ */
+ protected boolean daemon = true;
+ public void setDaemon(boolean b) { daemon = b; }
+ public boolean getDaemon() { return daemon; }
+
+
+ /**
+ * Name of the thread pool, which will be used for naming child threads.
+ */
+ protected String name = "TP";
+ public void setName(String name) { this.name = name; }
+ public String getName() { return name; }
+
+
+ /**
+ * Use endfile for sending static files.
+ */
+ protected boolean useSendfile = Library.APR_HAS_SENDFILE;
+ public void setUseSendfile(boolean useSendfile) { this.useSendfile = useSendfile; }
+ public boolean getUseSendfile() { return useSendfile; }
+
+
+ /**
+ * Allow comet request handling.
+ */
+ protected boolean useComet = true;
+ public void setUseComet(boolean useComet) { this.useComet = useComet; }
+ public boolean getUseComet() { return useComet; }
+
+
+ /**
+ * Acceptor thread count.
+ */
+ protected int acceptorThreadCount = 0;
+ public void setAcceptorThreadCount(int acceptorThreadCount) { this.acceptorThreadCount = acceptorThreadCount; }
+ public int getAcceptorThreadCount() { return acceptorThreadCount; }
+
+
+ /**
+ * Sendfile thread count.
+ */
+ protected int sendfileThreadCount = 0;
+ public void setSendfileThreadCount(int sendfileThreadCount) { this.sendfileThreadCount = sendfileThreadCount; }
+ public int getSendfileThreadCount() { return sendfileThreadCount; }
+
+
+ /**
+ * Poller thread count.
+ */
+ protected int pollerThreadCount = 0;
+ public void setPollerThreadCount(int pollerThreadCount) { this.pollerThreadCount = pollerThreadCount; }
+ public int getPollerThreadCount() { return pollerThreadCount; }
+
+ protected long selectorTimeout = 5000;
+ public void setSelectorTimeout(long timeout){ this.selectorTimeout = timeout;}
+ public long getSelectorTimeout(){ return this.selectorTimeout; }
+ /**
+ * The socket poller.
+ */
+ protected Poller[] pollers = null;
+ protected int pollerRoundRobin = 0;
+ public Poller getPoller() {
+ pollerRoundRobin = (pollerRoundRobin + 1) % pollers.length;
+ Poller poller = pollers[pollerRoundRobin];
+ poller.comet = false;
+ return poller;
+ }
+
+
+ /**
+ * The socket poller used for Comet support.
+ */
+ public Poller getCometPoller() {
+ Poller poller = getPoller();
+ poller.comet = true;
+ return poller;
+ }
+
+
+ /**
+ * The static file sender.
+ */
+ protected Sendfile[] sendfiles = null;
+ protected int sendfileRoundRobin = 0;
+ public Sendfile getSendfile() {
+ sendfileRoundRobin = (sendfileRoundRobin + 1) % sendfiles.length;
+ return sendfiles[sendfileRoundRobin];
+ }
+
+
+ /**
+ * Dummy maxSpareThreads property.
+ */
+ public int getMaxSpareThreads() { return 0; }
+
+
+ /**
+ * Dummy minSpareThreads property.
+ */
+ public int getMinSpareThreads() { return 0; }
+
+
+ /**
+ * SSL engine.
+ */
+ protected String SSLEngine = "off";
+ public String getSSLEngine() { return SSLEngine; }
+ public void setSSLEngine(String SSLEngine) { this.SSLEngine = SSLEngine; }
+
+
+ /**
+ * SSL protocols.
+ */
+ protected String SSLProtocol = "all";
+ public String getSSLProtocol() { return SSLProtocol; }
+ public void setSSLProtocol(String SSLProtocol) { this.SSLProtocol = SSLProtocol; }
+
+
+ /**
+ * SSL password (if a cert is encrypted, and no password has been provided, a callback
+ * will ask for a password).
+ */
+ protected String SSLPassword = null;
+ public String getSSLPassword() { return SSLPassword; }
+ public void setSSLPassword(String SSLPassword) { this.SSLPassword = SSLPassword; }
+
+
+ /**
+ * SSL cipher suite.
+ */
+ protected String SSLCipherSuite = "ALL";
+ public String getSSLCipherSuite() { return SSLCipherSuite; }
+ public void setSSLCipherSuite(String SSLCipherSuite) { this.SSLCipherSuite = SSLCipherSuite; }
+
+
+ /**
+ * SSL certificate file.
+ */
+ protected String SSLCertificateFile = null;
+ public String getSSLCertificateFile() { return SSLCertificateFile; }
+ public void setSSLCertificateFile(String SSLCertificateFile) { this.SSLCertificateFile = SSLCertificateFile; }
+
+
+ /**
+ * SSL certificate key file.
+ */
+ protected String SSLCertificateKeyFile = null;
+ public String getSSLCertificateKeyFile() { return SSLCertificateKeyFile; }
+ public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { this.SSLCertificateKeyFile = SSLCertificateKeyFile; }
+
+
+ /**
+ * SSL certificate chain file.
+ */
+ protected String SSLCertificateChainFile = null;
+ public String getSSLCertificateChainFile() { return SSLCertificateChainFile; }
+ public void setSSLCertificateChainFile(String SSLCertificateChainFile) { this.SSLCertificateChainFile = SSLCertificateChainFile; }
+
+
+ /**
+ * SSL CA certificate path.
+ */
+ protected String SSLCACertificatePath = null;
+ public String getSSLCACertificatePath() { return SSLCACertificatePath; }
+ public void setSSLCACertificatePath(String SSLCACertificatePath) { this.SSLCACertificatePath = SSLCACertificatePath; }
+
+
+ /**
+ * SSL CA certificate file.
+ */
+ protected String SSLCACertificateFile = null;
+ public String getSSLCACertificateFile() { return SSLCACertificateFile; }
+ public void setSSLCACertificateFile(String SSLCACertificateFile) { this.SSLCACertificateFile = SSLCACertificateFile; }
+
+
+ /**
+ * SSL CA revocation path.
+ */
+ protected String SSLCARevocationPath = null;
+ public String getSSLCARevocationPath() { return SSLCARevocationPath; }
+ public void setSSLCARevocationPath(String SSLCARevocationPath) { this.SSLCARevocationPath = SSLCARevocationPath; }
+
+
+ /**
+ * SSL CA revocation file.
+ */
+ protected String SSLCARevocationFile = null;
+ public String getSSLCARevocationFile() { return SSLCARevocationFile; }
+ public void setSSLCARevocationFile(String SSLCARevocationFile) { this.SSLCARevocationFile = SSLCARevocationFile; }
+
+
+ /**
+ * SSL verify client.
+ */
+ protected String SSLVerifyClient = "none";
+ public String getSSLVerifyClient() { return SSLVerifyClient; }
+ public void setSSLVerifyClient(String SSLVerifyClient) { this.SSLVerifyClient = SSLVerifyClient; }
+
+
+ /**
+ * SSL verify depth.
+ */
+ protected int SSLVerifyDepth = 10;
+ public int getSSLVerifyDepth() { return SSLVerifyDepth; }
+ public void setSSLVerifyDepth(int SSLVerifyDepth) { this.SSLVerifyDepth = SSLVerifyDepth; }
+
+
+ // --------------------------------------------------------- Public Methods
+
+
+ /**
+ * Number of keepalive sockets.
+ */
+ public int getKeepAliveCount() {
+ if (pollers == null) {
+ return 0;
+ } else {
+ int keepAliveCount = 0;
+ for (int i = 0; i < pollers.length; i++) {
+ keepAliveCount += pollers[i].getKeepAliveCount();
+ }
+ return keepAliveCount;
+ }
+ }
+
+
+ /**
+ * Number of sendfile sockets.
+ */
+ public int getSendfileCount() {
+ if (sendfiles == null) {
+ return 0;
+ } else {
+ int sendfileCount = 0;
+ for (int i = 0; i < sendfiles.length; i++) {
+ sendfileCount += sendfiles[i].getSendfileCount();
+ }
+ return sendfileCount;
+ }
+ }
+
+
+ /**
+ * Return the amount of threads that are managed by the pool.
+ *
+ * @return the amount of threads that are managed by the pool
+ */
+ public int getCurrentThreadCount() {
+ return curThreads;
+ }
+
+
+ /**
+ * Return the amount of threads currently busy.
+ *
+ * @return the amount of threads currently busy
+ */
+ public int getCurrentThreadsBusy() {
+ return curThreadsBusy;
+ }
+
+
+ /**
+ * Return the state of the endpoint.
+ *
+ * @return true if the endpoint is running, false otherwise
+ */
+ public boolean isRunning() {
+ return running;
+ }
+
+
+ /**
+ * Return the state of the endpoint.
+ *
+ * @return true if the endpoint is paused, false otherwise
+ */
+ public boolean isPaused() {
+ return paused;
+ }
+
+
+ // ----------------------------------------------- Public Lifecycle Methods
+
+
+ /**
+ * Initialize the endpoint.
+ */
+ public void init()
+ throws Exception {
+
+ if (initialized)
+ return;
+
+ serverSock = ServerSocketChannel.open();
+ InetSocketAddress addr = (address!=null?new InetSocketAddress(address,port):new InetSocketAddress(port));
+ serverSock.socket().bind(addr,100); //todo, set backlog value
+ serverSock.configureBlocking(true); //mimic APR behavior
+ // Sendfile usage on systems which don't support it cause major problems
+ if (useSendfile) {
+ log.warn(sm.getString("endpoint.sendfile.nosupport"));
+ useSendfile = false;
+ }
+
+ // Initialize thread count defaults for acceptor, poller and sendfile
+ if (acceptorThreadCount == 0) {
+ // FIXME: Doesn't seem to work that well with multiple accept threads
+ acceptorThreadCount = 1;
+ }
+ if (pollerThreadCount != 1) {
+ // limit to one poller, no need for others
+ pollerThreadCount = 1;
+ }
+ if (sendfileThreadCount != 0) {
+ sendfileThreadCount = 0;
+ }
+
+ // Initialize SSL if needed
+ if (!"off".equalsIgnoreCase(SSLEngine)) {
+ // Initialize SSL
+ // FIXME: one per VM call ?
+ if ("on".equalsIgnoreCase(SSLEngine)) {
+ SSL.initialize(null);
+ } else {
+ SSL.initialize(SSLEngine);
+ }
+ // SSL protocol
+ int value = SSL.SSL_PROTOCOL_ALL;
+ if ("SSLv2".equalsIgnoreCase(SSLProtocol)) {
+ value = SSL.SSL_PROTOCOL_SSLV2;
+ } else if ("SSLv3".equalsIgnoreCase(SSLProtocol)) {
+ value = SSL.SSL_PROTOCOL_SSLV3;
+ } else if ("TLSv1".equalsIgnoreCase(SSLProtocol)) {
+ value = SSL.SSL_PROTOCOL_TLSV1;
+ } else if ("SSLv2+SSLv3".equalsIgnoreCase(SSLProtocol)) {
+ value = SSL.SSL_PROTOCOL_SSLV2 | SSL.SSL_PROTOCOL_SSLV3;
+ }
+// // Create SSL Context
+// sslContext = SSLContext.make(rootPool, value, SSL.SSL_MODE_SERVER);
+// // List the ciphers that the client is permitted to negotiate
+// SSLContext.setCipherSuite(sslContext, SSLCipherSuite);
+// // Load Server key and certificate
+// SSLContext.setCertificate(sslContext, SSLCertificateFile, SSLCertificateKeyFile, SSLPassword, SSL.SSL_AIDX_RSA);
+// // Set certificate chain file
+// SSLContext.setCertificateChainFile(sslContext, SSLCertificateChainFile, false);
+// // Support Client Certificates
+// SSLContext.setCACertificate(sslContext, SSLCACertificateFile, SSLCACertificatePath);
+// // Set revocation
+// SSLContext.setCARevocation(sslContext, SSLCARevocationFile, SSLCARevocationPath);
+// // Client certificate verification
+// value = SSL.SSL_CVERIFY_NONE;
+// if ("optional".equalsIgnoreCase(SSLVerifyClient)) {
+// value = SSL.SSL_CVERIFY_OPTIONAL;
+// } else if ("require".equalsIgnoreCase(SSLVerifyClient)) {
+// value = SSL.SSL_CVERIFY_REQUIRE;
+// } else if ("optionalNoCA".equalsIgnoreCase(SSLVerifyClient)) {
+// value = SSL.SSL_CVERIFY_OPTIONAL_NO_CA;
+// }
+// SSLContext.setVerify(sslContext, value, SSLVerifyDepth);
+ // For now, sendfile is not supported with SSL
+ useSendfile = false;
+ }
+
+ initialized = true;
+
+ }
+
+
+ /**
+ * Start the APR endpoint, creating acceptor, poller and sendfile threads.
+ */
+ public void start()
+ throws Exception {
+ // Initialize socket if not done before
+ if (!initialized) {
+ init();
+ }
+ if (!running) {
+ running = true;
+ paused = false;
+
+ // Create worker collection
+ if (executor == null) {
+ workers = new WorkerStack(maxThreads);
+ }
+
+ // Start acceptor threads
+ for (int i = 0; i < acceptorThreadCount; i++) {
+ Thread acceptorThread = new Thread(new Acceptor(), getName() + "-Acceptor-" + i);
+ acceptorThread.setPriority(threadPriority);
+ acceptorThread.setDaemon(daemon);
+ acceptorThread.start();
+ }
+
+ // Start poller threads
+ pollers = new Poller[pollerThreadCount];
+ for (int i = 0; i < pollerThreadCount; i++) {
+ pollers[i] = new Poller(false);
+ pollers[i].init();
+ Thread pollerThread = new Thread(pollers[i], getName() + "-Poller-" + i);
+ pollerThread.setPriority(threadPriority);
+ pollerThread.setDaemon(true);
+ pollerThread.start();
+ }
+
+ // Start sendfile threads
+ if (useSendfile) {
+ sendfiles = new Sendfile[sendfileThreadCount];
+ for (int i = 0; i < sendfileThreadCount; i++) {
+ sendfiles[i] = new Sendfile();
+ sendfiles[i].init();
+ Thread sendfileThread = new Thread(sendfiles[i], getName() + "-Sendfile-" + i);
+ sendfileThread.setPriority(threadPriority);
+ sendfileThread.setDaemon(true);
+ sendfileThread.start();
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Pause the endpoint, which will make it stop accepting new sockets.
+ */
+ public void pause() {
+ if (running && !paused) {
+ paused = true;
+ unlockAccept();
+ }
+ }
+
+
+ /**
+ * Resume the endpoint, which will make it start accepting new sockets
+ * again.
+ */
+ public void resume() {
+ if (running) {
+ paused = false;
+ }
+ }
+
+
+ /**
+ * Stop the endpoint. This will cause all processing threads to stop.
+ */
+ public void stop() {
+ if (running) {
+ running = false;
+ unlockAccept();
+ for (int i = 0; i < pollers.length; i++) {
+ pollers[i].destroy();
+ }
+ pollers = null;
+ if (useSendfile) {
+ for (int i = 0; i < sendfiles.length; i++) {
+ sendfiles[i].destroy();
+ }
+ sendfiles = null;
+ }
+ }
+ }
+
+
+ /**
+ * Deallocate APR memory pools, and close server socket.
+ */
+ public void destroy() throws Exception {
+ if (running) {
+ stop();
+ }
+ // Close server socket
+ serverSock.socket().close();
+ serverSock.close();
+ serverSock = null;
+ sslContext = 0;
+ initialized = false;
+ }
+
+
+ // ------------------------------------------------------ Protected Methods
+
+
+ /**
+ * Get a sequence number used for thread naming.
+ */
+ protected int getSequence() {
+ return sequence++;
+ }
+
+
+ /**
+ * Unlock the server socket accept using a bugus connection.
+ */
+ protected void unlockAccept() {
+ java.net.Socket s = null;
+ try {
+ // Need to create a connection to unlock the accept();
+ if (address == null) {
+ s = new java.net.Socket("127.0.0.1", port);
+ } else {
+ s = new java.net.Socket(address, port);
+ // setting soLinger to a small value will help shutdown the
+ // connection quicker
+ s.setSoLinger(true, 0);
+ }
+ } catch(Exception e) {
+ if (log.isDebugEnabled()) {
+ log.debug(sm.getString("endpoint.debug.unlock", "" + port), e);
+ }
+ } finally {
+ if (s != null) {
+ try {
+ s.close();
+ } catch (Exception e) {
+ // Ignore
+ }
+ }
+ }
+ }
+
+
+ /**
+ * Process the specified connection.
+ */
+ protected boolean setSocketOptions(SocketChannel socket) {
+ // Process the connection
+ int step = 1;
+ try {
+ //disable blocking, APR style, we are gonna be polling it
+ socket.configureBlocking(false);
+
+ // 1: Set socket options: timeout, linger, etc
+ if (soLinger >= 0)
+ socket.socket().setSoLinger(true,soLinger);
+ if (tcpNoDelay)
+ socket.socket().setTcpNoDelay(true);
+ if (soTimeout > 0)
+ socket.socket().setSoTimeout(soTimeout);
+
+
+ // 2: SSL handshake
+ step = 2;
+ if (sslContext != 0) {
+// SSLSocket.attach(sslContext, socket);
+// if (SSLSocket.handshake(socket) != 0) {
+// if (log.isDebugEnabled()) {
+// log.debug(sm.getString("endpoint.err.handshake") + ": " + SSL.getLastError());
+// }
+// return false;
+// }
+ }
+
+ getPoller().register(socket);
+
+ } catch (Throwable t) {
+ if (log.isDebugEnabled()) {
+ if (step == 2) {
+ log.debug(sm.getString("endpoint.err.handshake"), t);
+ } else {
+ log.debug(sm.getString("endpoint.err.unexpected"), t);
+ }
+ }
+ // Tell to close the socket
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Create (or allocate) and return an available processor for use in
+ * processing a specific HTTP request, if possible. If the maximum
+ * allowed processors have already been created and are in use, return
+ * <code>null</code> instead.
+ */
+ protected Worker createWorkerThread() {
+
+ synchronized (workers) {
+ if (workers.size() > 0) {
+ curThreadsBusy++;
+ return (workers.pop());
+ }
+ if ((maxThreads > 0) && (curThreads < maxThreads)) {
+ curThreadsBusy++;
+ return (newWorkerThread());
+ } else {
+ if (maxThreads < 0) {
+ curThreadsBusy++;
+ return (newWorkerThread());
+ } else {
+ return (null);
+ }
+ }
+ }
+
+ }
+
+
+ /**
+ * Create and return a new processor suitable for processing HTTP
+ * requests and returning the corresponding responses.
+ */
+ protected Worker newWorkerThread() {
+
+ Worker workerThread = new Worker();
+ workerThread.start();
+ return (workerThread);
+
+ }
+
+
+ /**
+ * Return a new worker thread, and block while to worker is available.
+ */
+ protected Worker getWorkerThread() {
+ // Allocate a new worker thread
+ Worker workerThread = createWorkerThread();
+ while (workerThread == null) {
+ try {
+ synchronized (workers) {
+ workers.wait();
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ workerThread = createWorkerThread();
+ }
+ return workerThread;
+ }
+
+
+ /**
+ * Recycle the specified Processor so that it can be used again.
+ *
+ * @param workerThread The processor to be recycled
+ */
+ protected void recycleWorkerThread(Worker workerThread) {
+ synchronized (workers) {
+ workers.push(workerThread);
+ curThreadsBusy--;
+ workers.notify();
+ }
+ }
+
+
+ /**
+ * Allocate a new poller of the specified size.
+ */
+ protected long allocatePoller(int size, long pool, int timeout) {
+ try {
+ return Poll.create(size, pool, 0, timeout * 1000);
+ } catch (Error e) {
+ if (Status.APR_STATUS_IS_EINVAL(e.getError())) {
+ log.info(sm.getString("endpoint.poll.limitedpollsize", "" + size));
+ return 0;
+ } else {
+ log.error(sm.getString("endpoint.poll.initfail"), e);
+ return -1;
+ }
+ }
+ }
+
+
+ /**
+ * Process given socket.
+ */
+ protected boolean processSocket(SocketChannel socket) {
+ try {
+ if (executor == null) {
+ getWorkerThread().assign(socket);
+ } else {
+ executor.execute(new SocketProcessor(socket));
+ }
+ } catch (Throwable t) {
+ // This means we got an OOM or similar creating a thread, or that
+ // the pool and its queue are full
+ log.error(sm.getString("endpoint.process.fail"), t);
+ return false;
+ }
+ return true;
+ }
+
+
+ /**
+ * Process given socket for an event.
+ */
+ protected boolean processSocket(SocketChannel socket, boolean error) {
+ try {
+ if (executor == null) {
+ getWorkerThread().assign(socket, error);
+ } else {
+ executor.execute(new SocketEventProcessor(socket, error));
+ }
+ } catch (Throwable t) {
+ // This means we got an OOM or similar creating a thread, or that
+ // the pool and its queue are full
+ log.error(sm.getString("endpoint.process.fail"), t);
+ return false;
+ }
+ return true;
+ }
+
+
+ // --------------------------------------------------- Acceptor Inner Class
+
+
+ /**
+ * Server socket acceptor thread.
+ */
+ protected class Acceptor implements Runnable {
+
+
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+ // Loop until we receive a shutdown command
+ while (running) {
+
+ // Loop if endpoint is paused
+ while (paused) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ try {
+ // Accept the next incoming connection from the server socket
+ SocketChannel socket = serverSock.accept();
+ // Hand this socket off to an appropriate processor
+ if(!setSocketOptions(socket))
+ {
+ // Close socket right away
+ socket.socket().close();
+ socket.close();
+ }
+ } catch (Throwable t) {
+ log.error(sm.getString("endpoint.accept.fail"), t);
+ }
+
+ // The processor will recycle itself when it finishes
+
+ }
+
+ }
+
+ }
+
+
+ // ----------------------------------------------------- Poller Inner Class
+
+
+ /**
+ * Poller class.
+ */
+ public class Poller implements Runnable {
+
+ protected Selector selector;
+ protected LinkedList<Runnable> events = new LinkedList<Runnable>();
+ protected boolean close = false;
+ protected boolean comet = true;
+
+ protected int keepAliveCount = 0;
+ public int getKeepAliveCount() { return keepAliveCount; }
+
+
+
+ public Poller(boolean comet) throws IOException {
+ this.comet = comet;
+ this.selector = Selector.open();
+ }
+
+ public Selector getSelector() { return selector;}
+
+ /**
+ * Create the poller. With some versions of APR, the maximum poller size will
+ * be 62 (reocmpiling APR is necessary to remove this limitation).
+ */
+ protected void init() {
+ keepAliveCount = 0;
+ }
+
+ /**
+ * Destroy the poller.
+ */
+ protected void destroy() {
+ // Wait for polltime before doing anything, so that the poller threads
+ // exit, otherwise parallel descturction of sockets which are still
+ // in the poller can cause problems
+ try {
+ synchronized (this) {
+ this.wait(pollTime / 1000);
+ }
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ close = true;
+ }
+
+ /**
+ * Add specified socket and associated pool to the poller. The socket will
+ * be added to a temporary array, and polled first after a maximum amount
+ * of time equal to pollTime (in most cases, latency will be much lower,
+ * however).
+ *
+ * @param socket to add to the poller
+ */
+ public void add(final SocketChannel socket) {
+ final SelectionKey key = socket.keyFor(selector);
+ Runnable r = new Runnable() {
+ public void run() {
+ if ( key != null ) key.interestOps(SelectionKey.OP_READ);
+ }
+ };
+ synchronized (events) {
+ events.add(r);
+ }
+ selector.wakeup();
+ }
+
+ public void events() {
+ synchronized (events) {
+ Runnable r = null;
+ while ( (events.size() > 0) && (r = events.removeFirst()) != null ) {
+ try {
+ r.run();
+ } catch ( Exception x ) {
+ log.error("",x);
+ }
+ }
+ events.clear();
+ }
+ }
+
+ public void register(final SocketChannel socket)
+ {
+ SelectionKey key = socket.keyFor(selector);
+ Runnable r = new Runnable() {
+ public void run() {
+ try {
+ socket.register(selector, SelectionKey.OP_READ, new KeyAttachment());
+ } catch (Exception x) {
+ log.error("", x);
+ }
+ }
+
+ };
+ synchronized (events) {
+ events.add(r);
+ }
+ selector.wakeup();
+ }
+
+ public void cancelledKey(SelectionKey key) {
+ try {
+ KeyAttachment ka = (KeyAttachment) key.attachment();
+ key.cancel();
+ if (ka.getComet()) processSocket( (SocketChannel) key.channel(), true);
+ key.channel().close();
+ } catch (IOException e) {
+ if ( log.isDebugEnabled() ) log.debug("",e);
+ // Ignore
+ }
+ }
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+ // Loop until we receive a shutdown command
+ while (running) {
+ // Loop if endpoint is paused
+ while (paused) {
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ // Ignore
+ }
+ }
+
+ events();
+ // Time to terminate?
+ if (close) return;
+
+ int keyCount = 0;
+ try {
+ keyCount = selector.select(selectorTimeout);
+ } catch (IOException x) {
+ log.error("",x);
+ continue;
+ }
+ //timeout
+ Set keys = selector.keys();
+ long now = System.currentTimeMillis();
+ for (Iterator iter = keys.iterator(); iter.hasNext(); ) {
+ SelectionKey key = (SelectionKey) iter.next();
+ try {
+ if (key.interestOps() == SelectionKey.OP_READ) {
+ //only timeout sockets that we are waiting for a read from
+ KeyAttachment ka = (KeyAttachment) key.attachment();
+ long delta = now - ka.getLastAccess();
+ if (delta > (long) soTimeout) {
+ cancelledKey(key);
+ }
+ }
+ }catch ( CancelledKeyException ckx ) {
+ cancelledKey(key);
+ }
+ }
+
+
+ if (keyCount == 0) continue;
+
+ Iterator iterator = selector.selectedKeys().iterator();
+ // Walk through the collection of ready keys and dispatch
+ // any active event.
+ while (iterator.hasNext()) {
+ SelectionKey sk = (SelectionKey) iterator.next();
+ iterator.remove();
+ KeyAttachment attachment = (KeyAttachment)sk.attachment();
+ try {
+ if(attachment == null) attachment = new KeyAttachment();
+ attachment.access();
+ sk.attach(attachment);
+
+ int readyOps = sk.readyOps();
+ sk.interestOps(sk.interestOps() & ~readyOps);
+ SocketChannel channel = (SocketChannel)sk.channel();
+ boolean read = sk.isReadable();
+ if (read) {
+ if ( comet ) {
+ if (!processSocket(channel,false)) processSocket(channel,true);
+ } else {
+ boolean close = (!processSocket(channel));
+ if ( close ) {
+ channel.socket().close();
+ channel.close();
+ }
+ }
+ }
+ if (sk.isValid() && sk.isWritable()) {
+ }
+ } catch ( CancelledKeyException ckx ) {
+ if (attachment!=null && attachment.getComet()) processSocket( (SocketChannel) sk.channel(), true);
+ try {
+ sk.channel().close();
+ }catch ( Exception ignore){}
+ } catch (Throwable t) {
+ log.error("",t);
+ }
+ }//while
+
+
+ }
+ synchronized (this) {
+ this.notifyAll();
+ }
+
+ }
+
+ }
+
+ public static class KeyAttachment {
+
+ public long getLastAccess() { return lastAccess; }
+ public void access() { access(System.currentTimeMillis()); }
+ public void access(long access) { lastAccess = access; }
+ public void setComet(boolean comet) { this.comet = comet; }
+ public boolean getComet() { return comet; }
+ public boolean getCurrentAccess() { return currentAccess; }
+ public void setCurrentAccess(boolean access) { currentAccess = access; }
+
+ protected long lastAccess = System.currentTimeMillis();
+ protected boolean currentAccess = false;
+ protected boolean comet = false;
+
+ }
+
+
+
+ // ----------------------------------------------------- Worker Inner Class
+
+
+ /**
+ * Server processor class.
+ */
+ protected class Worker implements Runnable {
+
+
+ protected Thread thread = null;
+ protected boolean available = false;
+ protected SocketChannel socket = null;
+ protected boolean event = false;
+ protected boolean error = false;
+
+
+ /**
+ * Process an incoming TCP/IP connection on the specified socket. Any
+ * exception that occurs during processing must be logged and swallowed.
+ * <b>NOTE</b>: This method is called from our Connector's thread. We
+ * must assign it to our own thread so that multiple simultaneous
+ * requests can be handled.
+ *
+ * @param socket TCP socket to process
+ */
+ protected synchronized void assign(SocketChannel socket) {
+
+ // Wait for the Processor to get the previous Socket
+ while (available) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Store the newly available Socket and notify our thread
+ this.socket = socket;
+ event = false;
+ error = false;
+ available = true;
+ notifyAll();
+
+ }
+
+
+ protected synchronized void assign(SocketChannel socket, boolean error) {
+
+ // Wait for the Processor to get the previous Socket
+ while (available) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Store the newly available Socket and notify our thread
+ this.socket = socket;
+ event = true;
+ this.error = error;
+ available = true;
+ notifyAll();
+ }
+
+
+ /**
+ * Await a newly assigned Socket from our Connector, or <code>null</code>
+ * if we are supposed to shut down.
+ */
+ protected synchronized SocketChannel await() {
+
+ // Wait for the Connector to provide a new Socket
+ while (!available) {
+ try {
+ wait();
+ } catch (InterruptedException e) {
+ }
+ }
+
+ // Notify the Connector that we have received this Socket
+ SocketChannel socket = this.socket;
+ available = false;
+ notifyAll();
+
+ return (socket);
+
+ }
+
+
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+ // Process requests until we receive a shutdown signal
+ while (running) {
+
+ // Wait for the next socket to be assigned
+ SocketChannel socket = await();
+ if (socket == null)
+ continue;
+
+ // Process the request from this socket
+ if ((event) && (handler.event(socket, error) == Handler.SocketState.CLOSED)) {
+ // Close socket and pool
+ try {
+ socket.socket().close();
+ socket.close();
+ }catch ( Exception x ) {
+ log.error("",x);
+ }
+ } else if ((!event) && (handler.process(socket) == Handler.SocketState.CLOSED)) {
+ // Close socket and pool
+ try {
+ socket.socket().close();
+ socket.close();
+ }catch ( Exception x ) {
+ log.error("",x);
+ }
+ }
+
+ // Finish up this request
+ recycleWorkerThread(this);
+
+ }
+
+ }
+
+
+ /**
+ * Start the background processing thread.
+ */
+ public void start() {
+ thread = new Thread(this);
+ thread.setName(getName() + "-" + (++curThreads));
+ thread.setDaemon(true);
+ thread.start();
+ }
+
+
+ }
+
+
+ // ----------------------------------------------- SendfileData Inner Class
+
+
+ /**
+ * SendfileData class.
+ */
+ public static class SendfileData {
+ // File
+ public String fileName;
+ public long fd;
+ public long fdpool;
+ // Range information
+ public long start;
+ public long end;
+ // Socket and socket pool
+ public SocketChannel socket;
+ // Position
+ public long pos;
+ // KeepAlive flag
+ public boolean keepAlive;
+ }
+
+
+ // --------------------------------------------------- Sendfile Inner Class
+
+
+ /**
+ * Sendfile class.
+ */
+ public class Sendfile implements Runnable {
+
+ protected long sendfilePollset = 0;
+ protected long pool = 0;
+ protected long[] desc;
+ protected HashMap<Long, SendfileData> sendfileData;
+
+ protected int sendfileCount;
+ public int getSendfileCount() { return sendfileCount; }
+
+ protected ArrayList<SendfileData> addS;
+
+ /**
+ * Create the sendfile poller. With some versions of APR, the maximum poller size will
+ * be 62 (reocmpiling APR is necessary to remove this limitation).
+ */
+ protected void init() {
+// pool = Pool.create(serverSockPool);
+// int size = sendfileSize / sendfileThreadCount;
+// sendfilePollset = allocatePoller(size, pool, soTimeout);
+// if (sendfilePollset == 0 && size > 1024) {
+// size = 1024;
+// sendfilePollset = allocatePoller(size, pool, soTimeout);
+// }
+// if (sendfilePollset == 0) {
+// size = 62;
+// sendfilePollset = allocatePoller(size, pool, soTimeout);
+// }
+// desc = new long[size * 2];
+// sendfileData = new HashMap<Long, SendfileData>(size);
+// addS = new ArrayList<SendfileData>();
+ }
+
+ /**
+ * Destroy the poller.
+ */
+ protected void destroy() {
+// // Wait for polltime before doing anything, so that the poller threads
+// // exit, otherwise parallel descturction of sockets which are still
+// // in the poller can cause problems
+// try {
+// synchronized (this) {
+// this.wait(pollTime / 1000);
+// }
+// } catch (InterruptedException e) {
+// // Ignore
+// }
+// // Close any socket remaining in the add queue
+// for (int i = (addS.size() - 1); i >= 0; i--) {
+// SendfileData data = addS.get(i);
+// Socket.destroy(data.socket);
+// }
+// // Close all sockets still in the poller
+// int rv = Poll.pollset(sendfilePollset, desc);
+// if (rv > 0) {
+// for (int n = 0; n < rv; n++) {
+// Socket.destroy(desc[n*2+1]);
+// }
+// }
+// Pool.destroy(pool);
+// sendfileData.clear();
+ }
+
+ /**
+ * Add the sendfile data to the sendfile poller. Note that in most cases,
+ * the initial non blocking calls to sendfile will return right away, and
+ * will be handled asynchronously inside the kernel. As a result,
+ * the poller will never be used.
+ *
+ * @param data containing the reference to the data which should be snet
+ * @return true if all the data has been sent right away, and false
+ * otherwise
+ */
+ public boolean add(SendfileData data) {
+// // Initialize fd from data given
+// try {
+// data.fdpool = Socket.pool(data.socket);
+// data.fd = File.open
+// (data.fileName, File.APR_FOPEN_READ
+// | File.APR_FOPEN_SENDFILE_ENABLED | File.APR_FOPEN_BINARY,
+// 0, data.fdpool);
+// data.pos = data.start;
+// // Set the socket to nonblocking mode
+// Socket.timeoutSet(data.socket, 0);
+// while (true) {
+// long nw = Socket.sendfilen(data.socket, data.fd,
+// data.pos, data.end - data.pos, 0);
+// if (nw < 0) {
+// if (!(-nw == Status.EAGAIN)) {
+// Socket.destroy(data.socket);
+// data.socket = 0;
+// return false;
+// } else {
+// // Break the loop and add the socket to poller.
+// break;
+// }
+// } else {
+// data.pos = data.pos + nw;
+// if (data.pos >= data.end) {
+// // Entire file has been sent
+// Pool.destroy(data.fdpool);
+// // Set back socket to blocking mode
+// Socket.timeoutSet(data.socket, soTimeout * 1000);
+// return true;
+// }
+// }
+// }
+// } catch (Exception e) {
+// log.error(sm.getString("endpoint.sendfile.error"), e);
+// return false;
+// }
+// // Add socket to the list. Newly added sockets will wait
+// // at most for pollTime before being polled
+// synchronized (this) {
+// addS.add(data);
+// this.notify();
+// }
+ return false;
+ }
+
+ /**
+ * Remove socket from the poller.
+ *
+ * @param data the sendfile data which should be removed
+ */
+ protected void remove(SendfileData data) {
+// int rv = Poll.remove(sendfilePollset, data.socket);
+// if (rv == Status.APR_SUCCESS) {
+// sendfileCount--;
+// }
+// sendfileData.remove(data);
+ }
+
+ /**
+ * The background thread that listens for incoming TCP/IP connections and
+ * hands them off to an appropriate processor.
+ */
+ public void run() {
+
+// // Loop until we receive a shutdown command
+// while (running) {
+//
+// // Loop if endpoint is paused
+// while (paused) {
+// try {
+// Thread.sleep(1000);
+// } catch (InterruptedException e) {
+// // Ignore
+// }
+// }
+//
+// while (sendfileCount < 1 && addS.size() < 1) {
+// try {
+// synchronized (this) {
+// this.wait();
+// }
+// } catch (InterruptedException e) {
+// // Ignore
+// }
+// }
+//
+// try {
+// // Add socket to the poller
+// if (addS.size() > 0) {
+// synchronized (this) {
+// for (int i = (addS.size() - 1); i >= 0; i--) {
+// SendfileData data = addS.get(i);
+// int rv = Poll.add(sendfilePollset, data.socket, Poll.APR_POLLOUT);
+// if (rv == Status.APR_SUCCESS) {
+// sendfileData.put(new Long(data.socket), data);
+// sendfileCount++;
+// } else {
+// log.warn(sm.getString("endpoint.sendfile.addfail", "" + rv, Error.strerror(rv)));
+// // Can't do anything: close the socket right away
+// Socket.destroy(data.socket);
+// }
+// }
+// addS.clear();
+// }
+// }
+// // Pool for the specified interval
+// int rv = Poll.poll(sendfilePollset, pollTime, desc, false);
+// if (rv > 0) {
+// for (int n = 0; n < rv; n++) {
+// // Get the sendfile state
+// SendfileData state =
+// sendfileData.get(new Long(desc[n*2+1]));
+// // Problem events
+// if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+// || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)) {
+// // Close socket and clear pool
+// remove(state);
+// // Destroy file descriptor pool, which should close the file
+// // Close the socket, as the reponse would be incomplete
+// Socket.destroy(state.socket);
+// continue;
+// }
+// // Write some data using sendfile
+// long nw = Socket.sendfilen(state.socket, state.fd,
+// state.pos,
+// state.end - state.pos, 0);
+// if (nw < 0) {
+// // Close socket and clear pool
+// remove(state);
+// // Close the socket, as the reponse would be incomplete
+// // This will close the file too.
+// Socket.destroy(state.socket);
+// continue;
+// }
+//
+// state.pos = state.pos + nw;
+// if (state.pos >= state.end) {
+// remove(state);
+// if (state.keepAlive) {
+// // Destroy file descriptor pool, which should close the file
+// Pool.destroy(state.fdpool);
+// Socket.timeoutSet(state.socket, soTimeout * 1000);
+// // If all done hand this socket off to a worker for
+// // processing of further requests
+// if (!processSocket(state.socket)) {
+// Socket.destroy(state.socket);
+// }
+// } else {
+// // Close the socket since this is
+// // the end of not keep-alive request.
+// Socket.destroy(state.socket);
+// }
+// }
+// }
+// } else if (rv < 0) {
+// int errn = -rv;
+// /* Any non timeup or interrupted error is critical */
+// if ((errn != Status.TIMEUP) && (errn != Status.EINTR)) {
+// if (errn > Status.APR_OS_START_USERERR) {
+// errn -= Status.APR_OS_START_USERERR;
+// }
+// log.error(sm.getString("endpoint.poll.fail", "" + errn, Error.strerror(errn)));
+// // Handle poll critical failure
+// synchronized (this) {
+// destroy();
+// init();
+// }
+// continue;
+// }
+// }
+// /* TODO: See if we need to call the maintain for sendfile poller */
+// } catch (Throwable t) {
+// log.error(sm.getString("endpoint.poll.error"), t);
+// }
+// }
+//
+// synchronized (this) {
+// this.notifyAll();
+// }
+
+ }
+
+ }
+
+
+ // ------------------------------------------------ Handler Inner Interface
+
+
+ /**
+ * Bare bones interface used for socket processing. Per thread data is to be
+ * stored in the ThreadWithAttributes extra folders, or alternately in
+ * thread local fields.
+ */
+ public interface Handler {
+ public enum SocketState {
+ OPEN, CLOSED, LONG
+ }
+ public SocketState process(SocketChannel socket);
+ public SocketState event(SocketChannel socket, boolean error);
+ }
+
+
+ // ------------------------------------------------- WorkerStack Inner Class
+
+
+ public class WorkerStack {
+
+ protected Worker[] workers = null;
+ protected int end = 0;
+
+ public WorkerStack(int size) {
+ workers = new Worker[size];
+ }
+
+ /**
+ * Put the object into the queue.
+ *
+ * @param object the object to be appended to the queue (first element).
+ */
+ public void push(Worker worker) {
+ workers[end++] = worker;
+ }
+
+ /**
+ * Get the first object out of the queue. Return null if the queue
+ * is empty.
+ */
+ public Worker pop() {
+ if (end > 0) {
+ return workers[--end];
+ }
+ return null;
+ }
+
+ /**
+ * Get the first object out of the queue, Return null if the queue
+ * is empty.
+ */
+ public Worker peek() {
+ return workers[end];
+ }
+
+ /**
+ * Is the queue empty?
+ */
+ public boolean isEmpty() {
+ return (end == 0);
+ }
+
+ /**
+ * How many elements are there in this queue?
+ */
+ public int size() {
+ return (end);
+ }
+ }
+
+
+ // ---------------------------------------------- SocketProcessor Inner Class
+
+
+ /**
+ * This class is the equivalent of the Worker, but will simply use in an
+ * external Executor thread pool.
+ */
+ protected class SocketProcessor implements Runnable {
+
+ protected SocketChannel socket = null;
+
+ public SocketProcessor(SocketChannel socket) {
+ this.socket = socket;
+ }
+
+ public void run() {
+
+ // Process the request from this socket
+ if (handler.process(socket) == Handler.SocketState.CLOSED) {
+ // Close socket and pool
+ try {
+ socket.socket().close();
+ socket.close();
+ } catch ( Exception x ) {
+ log.error("",x);
+ }
+ socket = null;
+ }
+
+ }
+
+ }
+
+
+ // --------------------------------------- SocketEventProcessor Inner Class
+
+
+ /**
+ * This class is the equivalent of the Worker, but will simply use in an
+ * external Executor thread pool.
+ */
+ protected class SocketEventProcessor implements Runnable {
+
+ protected SocketChannel socket = null;
+ protected boolean error = false;
+
+ public SocketEventProcessor(SocketChannel socket, boolean error) {
+ this.socket = socket;
+ this.error = error;
+ }
+
+ public void run() {
+
+ // Process the request from this socket
+ if (handler.event(socket, error) == Handler.SocketState.CLOSED) {
+ // Close socket and pool
+ try {
+ socket.socket().close();
+ socket.close();
+ } catch ( Exception x ) {
+ log.error("",x);
+ }
+ socket = null;
+ }
+
+ }
+
+ }
+
+
+}