Non blocking polling information.
authorfhanik <fhanik@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 22 Jun 2006 00:48:53 +0000 (00:48 +0000)
committerfhanik <fhanik@13f79535-47bb-0310-9956-ffa450edef68>
Thu, 22 Jun 2006 00:48:53 +0000 (00:48 +0000)
This implementation in pure Java NIO is almost a mimic of the APR implementation. It blocks on read and write, but has non blocking polling capabilities. Currently the read/write blocking is "busy" blocking, but I will see if I can simply configure blocking for the socket and if that would still allow the poller to work as expected.
This makes it a suitable connector for comet style protocols and where APR is not desired or available.

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc6.0.x/trunk@416187 13f79535-47bb-0310-9956-ffa450edef68

java/org/apache/catalina/connector/Connector.java
java/org/apache/coyote/http11/Http11NioProcessor.java [new file with mode: 0644]
java/org/apache/coyote/http11/Http11NioProtocol.java [new file with mode: 0644]
java/org/apache/coyote/http11/InternalNioInputBuffer.java [new file with mode: 0644]
java/org/apache/coyote/http11/InternalNioOutputBuffer.java [new file with mode: 0644]
java/org/apache/tomcat/util/net/NioEndpoint.java [new file with mode: 0644]

index 2e32c82..2e35525 100644 (file)
@@ -623,7 +623,9 @@ public class Connector
         }\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
@@ -636,7 +638,9 @@ public class Connector
                     ("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
diff --git a/java/org/apache/coyote/http11/Http11NioProcessor.java b/java/org/apache/coyote/http11/Http11NioProcessor.java
new file mode 100644 (file)
index 0000000..55e4bdb
--- /dev/null
@@ -0,0 +1,1797 @@
+/*\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
diff --git a/java/org/apache/coyote/http11/Http11NioProtocol.java b/java/org/apache/coyote/http11/Http11NioProtocol.java
new file mode 100644 (file)
index 0000000..b0d105b
--- /dev/null
@@ -0,0 +1,775 @@
+/*\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
diff --git a/java/org/apache/coyote/http11/InternalNioInputBuffer.java b/java/org/apache/coyote/http11/InternalNioInputBuffer.java
new file mode 100644 (file)
index 0000000..2f229ef
--- /dev/null
@@ -0,0 +1,827 @@
+/*\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
diff --git a/java/org/apache/coyote/http11/InternalNioOutputBuffer.java b/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
new file mode 100644 (file)
index 0000000..3dfb331
--- /dev/null
@@ -0,0 +1,783 @@
+/*\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
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
new file mode 100644 (file)
index 0000000..96dda4f
--- /dev/null
@@ -0,0 +1,1845 @@
+/*
+ *  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;
+            }
+
+        }
+
+    }
+
+
+}