Tag 8.0.16

git-svn-id: https://svn.apache.org/repos/asf/tomcat/tc8.0.x/tags/TOMCAT_8_0_16@1649495 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/BUILDING.txt b/BUILDING.txt
index ffa1c43..776381f 100644
--- a/BUILDING.txt
+++ b/BUILDING.txt
@@ -20,7 +20,7 @@
             ====================================================
 
 This subproject contains the source code for Tomcat @VERSION_MAJOR_MINOR@, a container that
-implements the Servlet 4.0, JSP 2.4?, EL 3.1? and WebSocket 1.2? specifications
+implements the Servlet 3.1, JSP 2.3, EL 3.0 and WebSocket 1.1 specifications
 from the Java Community Process <http://www.jcp.org/>.
 
 Note: If you just need to run Apache Tomcat, it is not necessary to build
@@ -35,7 +35,7 @@
 
  1. If the JDK is already installed, skip to (2).
 
- 2. Download a version 8 of Java Development Kit (JDK) release (use the
+ 2. Download a version 7 of Java Development Kit (JDK) release (use the
     latest update available for your chosen version) from one of:
 
         http://www.oracle.com/technetwork/java/javase/downloads/index.html
@@ -64,13 +64,9 @@
     into which you installed the JDK release.
 
 
-(2) Install Apache Ant version 1.9.3 or later on your computer.
+(2) Install Apache Ant version 1.8.2 or later on your computer
 
-    Note: Ant 1.9.4 has a regression and cannot be used for Tomcat's
-    release build (Ant bug 56641).
-
- 1. If Apache Ant version 1.9.3 or later is already installed on your
-    computer, skip to (3).
+ 1. If Apache Ant version 1.8.2 or later is already installed on your computer, skip to (3).
 
  2. Download a binary distribution of Ant from:
 
@@ -303,14 +299,15 @@
 
     output/build/logs
 
-By default the testsuite is run three times to test 3 different
-implementations of Tomcat connectors: NIO, NIO2 and APR. (If you are not
+By default the testsuite is run four times to test 4 different
+implementations of Tomcat connectors: BIO, NIO, NIO2 and APR. (If you are not
 familiar with Tomcat connectors, see config/http.html in documentation for
 details).
 
-The 3 runs are enabled and disabled individually by the following
+The 4 runs are enabled and disabled individually by the following
 properties, which all are "true" by default:
 
+    execute.test.bio=true
     execute.test.nio=true
     execute.test.nio2=true
     execute.test.apr=true
diff --git a/NOTICE b/NOTICE
index 2e3aa65..17e85bd 100644
--- a/NOTICE
+++ b/NOTICE
@@ -15,6 +15,13 @@
 related information is available at
 http://www.eclipse.org.
 
+For the bayeux implementation
+The org.apache.cometd.bayeux API is derivative work originating at the Dojo Foundation
+* Copyright 2007-2008 Guy Molinari
+* Copyright 2007-2008 Filip Hanik
+* Copyright 2007 Dojo Foundation
+* Copyright 2007 Mort Bay Consulting Pty. Ltd.
+
 The original XML Schemas for Java EE Deployment Descriptors:
  - javaee_5.xsd
  - javaee_web_services_1_2.xsd
diff --git a/RELEASE-NOTES b/RELEASE-NOTES
index 65788e5..40052c3 100644
--- a/RELEASE-NOTES
+++ b/RELEASE-NOTES
@@ -38,7 +38,7 @@
 ===================
 Dependency Changes:
 ===================
-Tomcat @VERSION_MAJOR_MINOR@ is designed to run on Java SE 8 and later.
+Tomcat @VERSION_MAJOR_MINOR@ is designed to run on Java SE 7 and later.
 
 
 ==============
@@ -52,7 +52,7 @@
 The public interfaces for the following classes may be added to in order to
 resolve bugs and/or add new features. No existing interface method will be
 removed or changed although it may be deprecated.
-- None
+- org.apache.catalina.* (excluding sub-packages)
 
 Note: As Tomcat @VERSION_MAJOR@ matures, the above list will be added to. The list is not
       considered complete at this time.
diff --git a/RUNNING.txt b/RUNNING.txt
index 546fbe4..ca7486a 100644
--- a/RUNNING.txt
+++ b/RUNNING.txt
@@ -20,16 +20,16 @@
             ===================================================
 
 Apache Tomcat @VERSION_MAJOR_MINOR@ requires a Java Standard Edition Runtime
-Environment (JRE) version 8 or later.
+Environment (JRE) version 7 or later.
 
 =============================
-Running With JRE 8 Or Later
+Running With JRE 7 Or Later
 =============================
 
 (1) Download and Install a Java SE Runtime Environment (JRE)
 
 (1.1) Download a Java SE Runtime Environment (JRE),
-      release version 8 or later, from
+      release version 7 or later, from
       http://www.oracle.com/technetwork/java/javase/downloads/index.html
 
 (1.2) Install the JRE according to the instructions included with the
@@ -159,7 +159,7 @@
 
 On Windows, %CATALINA_BASE%\bin\setenv.bat:
 
-  set "JRE_HOME=%ProgramFiles%\Java\jre8"
+  set "JRE_HOME=%ProgramFiles%\Java\jre7"
   exit /b 0
 
 On *nix, $CATALINA_BASE/bin/setenv.sh:
diff --git a/TOMCAT-NEXT.txt b/TOMCAT-NEXT.txt
index 772097c..cafcbb3 100644
--- a/TOMCAT-NEXT.txt
+++ b/TOMCAT-NEXT.txt
@@ -15,38 +15,210 @@
   limitations under the License.
 ================================================================================
 
-Notes of things to consider for the next major Tomcat release (9.0.x)
+Notes of things to consider for the next major Tomcat release (probably 8.0.x
+but possibly 7.1.x).
 
- 1. Fix Java 8 Javadoc warnings. Currently ~2800.
+ 1. Refactor the TLD parsing. TLDs are currently parsed twice. Once by Catalina
+    looking for listeners and once by Jasper.
+    - Complete
 
- 2. DONE.
-    Remove BIO AJP and HTTP connector.
+ 2. Refactor the XML parsing (org.apache.tomcat.util.xml ?) to remove duplicate
+    XML parsing code in Catalina and Jasper such as the entity resolvers used
+    for validation.
+    - Complete
 
- 3. DONE.
-    Remove Comet support.
+ 3. TLDs may have a many to many relationship between URIs and TLD files. This
+    can result in the same TLD file being parsed many times. Refactor the
+    TldLocationCache to cache the parsed nodes (will need to check for changes
+    to TLD files).
+    - Complete
 
- 4. Refactor the connectors to minimise code duplication.
-    - All implementation specific per connector code -> Endpoint
-    - All implementation specific per connection code -> SocketWrapper
+ 4. TLD files should be included in the dependencies for JSP and Tag files.
+    - Complete
 
- 5. SNI support for JSSE.
+ 5. Run the unused code detector and remove everything that isn't currently used.
+    Add deprecation markers for the removed code to Tomcat 7.0.x
+    - Complete
 
- 6. See what Java 8 language features we want to use.
+ 6. Change the default URIEncoding on the connector to UTF-8.
+    - Complete
 
- 7. Connector refactoring required for HTTP2/SPDY APIs that might be exposed in
-    the Servlet API.
+ 7. Rip out all the JNDI code in resource handling and replace it with straight
+    URLs (File or WAR).
+    - Complete
 
- 8. Keep an eye on the other Java EE 8 EGs (no sign of any movement apart
-    from the Servlet EG so far).
+    kkolinko: I think this proposal goes too far. There are several
+    separate issues. There are:
 
- 9. Refactor WebSocket I/O to go directly to Tomcat's internals rather than via
-    the Servlet API.
+    a) Internal API to define resources
+     - BaseDirContext implementing aliases and resource jars,
+     and there will be overlays in Servlet 3.1
+     - StandardContext.setResources() allowing an arbitrary DirContext
+     implementation via <Resources> element.
 
-10. Remove the use of system properties to control configuration wherever
-    possible.
+     Concerns:
+     - Too many ways to configure it.
 
-11. Reduce instances of setters and getters for the same property existing on an
-    object and its parent. This may require new objects to be exposed via JMX.
+    b) Internal API to lookup resources
+     - DirContext interface
 
-12. Consider wrapping the SocketWrapper with a facade to detect / prevent
-    components retaining references longer than they should.
+     Concerns:
+     - Unnecessary objects, e.g. NamingException instead of null.
+
+     - Too many methods. Name vs. String. list() vs. listBindings().
+
+     - Limited API. As a workaround, there are non-standard methods that
+       are implemented on BaseDirContext instead, e.g. getRealPath(),
+       doListBindings(..).
+
+     - All caching (ProxyDirContext) and aliases handling is
+     performed on the root level only.
+
+     Once I do a lookup that returns a DirContext, further lookups on it
+     will bypass the caching and aliases.
+
+    c) WebappClassLoader and its interaction with resources
+
+     WebappClassLoader uses DirContext API to access resources (classes,
+     jars).
+
+     Note that it has to construct a classpath for Java compiler called by
+     Jasper. The compiler cannot operate on a DirContext and needs access
+     to actual files and JARs.
+
+     Concerns:
+     - There are problems with access to classes and JAR files in
+     non-unpacked WARs.
+
+     It is resolved by unzipping the files into the working directory (in
+     WebappLoader#setRepositories()).
+
+     Note that DirContext is not notified of this copying.
+     StandardJarScanner does not know of those copies either.
+
+     - There are problems when the classes directory is served from
+     multiple locations
+
+     It seems to be worked around by adding the path of the alternative
+     classes directory to virtualClasspath of VirtualWebappLoader (as shown
+     by example in config/context.html#Virtual_webapp), but it is likely
+     that I miss something.
+
+     - antiJARLocking support in WebappClassLoader creates copies of
+     resources, but does not notify the DirContext.
+
+     - WebappClassLoader.jarFiles is used to track JAR files and keep them
+     open. These might be useful when looking for resources in those files.
+     These might be useful for StandardJarScanner.
+
+    d) StandardJarScanner
+
+     Concerns:
+     - It essentially scans the same JARs as accessed by WebappClassLoader.
+
+     It might be better to access them via WebappClassLoader rather that
+     through Servlet API methods.
+
+     The scanner is used by Jasper as well, so there are some concerns to
+     keep the components independent.
+
+    e) URL returned by ServletContext.getResource()
+     jndi:/hostName/contextPath/resourcePath
+
+     Goodies:
+     - Uniform URL space. Both files and directories can be represented,
+     hiding details of aliases, resource jars, etc.
+
+     - It hides implementation details.
+
+     - Permissions can be expressed as JndiPermission. They do not depend
+     on context version number.
+
+     - Accessing a resource through such URL leverages the cache
+     implemented in ProxyDirContext class. We do not access the file system
+     directly, nor need to open a JAR file.
+
+     - It can be created from a String if necessary.
+
+     Such use relies on DirContextURLStreamHandler.bindThread(..) being
+     called earlier in the same thread.
+
+     Concerns:
+     - Some components would like to get "real" resource URL from this one.
+
+     Maybe it can be exposed through some special header name,
+     DirContextURLConnection.getHeaderField(str)?
+
+     How such real URL can be prepared?
+     DirContext.getNameInNamespace()?
+     BaseDirContext.getRealPath()?
+     ((FileResourceAttributes)DirContext.getAttributes()).getCanonicalPath()?
+
+     - A resource lookup is performed twice. The first time in
+     ServletContext.getResource() (implemented in ApplicationContext.getResource())
+     to return null for non-existing paths.
+     The second time in DirContextURLConnection.connect().
+
+     It is good that there is a cache in ProxyDirContext that saves time
+     for repeated lookups.
+
+     - Using URLs involves encoding/decoding.
+
+     If there were some other API to access resources in a web application,
+     I would prefer some opaque object that allows access to resource
+     properties, but is converted to string/url form only on demand.
+
+    f) Connection, created from jndi: URL
+     DirContextURLStreamHandler, DirContextURLConnection
+
+     Goodies:
+     - DirContextURLConnection provides information about resource via
+     methods such as getContentLength(), getHeaderField(str).
+
+     Concerns:
+     - It exposes DirContext through some APIs, such as
+     DirContextURLConnection.getContent().
+
+     Is this feature going to be preserved for compatibility, or to be
+     removed?
+
+     - DirContextURLConnection.list(): a public method, that is not part of
+     the usual URL API. So URL API is lacking. Maybe some other methods
+     could be added.
+
+     A possible candidate could be "isCollection()", instead of asking for
+     getContentType() and checking its value against ResourceAttributes.COLLECTION_TYPE.
+
+    Threads:
+    http://markmail.org/thread/hqbmdn2qs6xcooko
+
+ 8. Review the connector shutdown code for timing and threading issues
+    particularly any that may result in a client socket being left open after a
+    connector.stop().
+    - Complete.
+
+ 9. Remove the svn keywords from all the files. (Just Java files?)
+    - Complete.
+
+10. Code to the interfaces in the o.a.catalina package and avoid coding directly
+    to implementations in other packages. This is likely to require a lot of
+    work. Maybe use Structure 101 (or similar) to help.
+    - Partially complete - probably as far as is practical.
+
+11. Merge Service and Engine
+    - Decided not to do this.
+
+12. Java 7 updates
+    - Use of <> operator where possible
+      - Complete
+    - Use of try with resources
+      - Complete
+    - Catching multiple exceptions
+      - Started
+        - javax.[annotation to el] complete
+        - remainder TODO
+
+13. Fix all FindBugs warnings
+    - Complete
+
+14. Review date formatting with a view to reducing duplication.
diff --git a/build.properties.default b/build.properties.default
index 07165a1..07c8514 100644
--- a/build.properties.default
+++ b/build.properties.default
@@ -23,15 +23,16 @@
 # -----------------------------------------------------------------------------
 
 # ----- Version Control Flags -----
-version.major=9
+version.major=8
 version.minor=0
-version.build=0
+version.build=16
 version.patch=0
 version.suffix=
 
 # ----- Build control flags -----
 # Note enabling validation uses Checkstyle which is LGPL licensed
 execute.validate=false
+execute.test.bio=true
 execute.test.nio=true
 execute.test.nio2=true
 # Still requires APR/native library to be present
@@ -65,8 +66,8 @@
 #base.path=C:/path/to/the/repository
 #base.path=/usr/local
 
-compile.source=1.8
-compile.target=1.8
+compile.source=1.7
+compile.target=1.7
 compile.debug=true
 
 # Do not pass -deprecation (-Xlint:deprecation) flag to javac
@@ -86,9 +87,6 @@
 # Mirror, was used when there were problems with the main SF downloads site
 # base-sf.loc=http://sunet.dl.sourceforge.net
 
-# Can be removed once we no longer require a Cobertura snapshot
-base-sonatype-snapshots=https://oss.sonatype.org/content/repositories/snapshots
-
 # ----- Commons Logging, version 1.1 or later -----
 # If this version is updated, check the versions required for the dependencies below
 # - avalon-framework
@@ -220,12 +218,22 @@
 checkstyle.loc=${base-sf.loc}/checkstyle/checkstyle/${checkstyle.version}/checkstyle-${checkstyle.version}-all.jar
 checkstyle.jar=${checkstyle.home}/checkstyle-${checkstyle.version}-all.jar
 
+# ----- JSON Libraries (for bayeux module) -----
+json-lib.home=${base.path}/json-20080701
+json-lib.lib=http://repo1.maven.org/maven2/org/json/json/20080701/json-20080701.jar
+json-lib.jar=json.jar
+
+# ----- Dojo Toolkit (for bayeux module) -----
+dojo-js.home=${base.path}/dojo-release-1.1.1
+dojo-js.loc=http://download.dojotoolkit.org/release-1.1.1/dojo-release-1.1.1.tar.gz
+dojo-js.jar=${dojo-js.home}/dojo/dojo.js
+
 # ----- Cobertura code coverage tool -----
-cobertura.version=2.1.0-SNAPSHOT
+cobertura.version=2.0.3
 cobertura.home=${base.path}/cobertura-${cobertura.version}
 cobertura.jar=${cobertura.home}/cobertura-${cobertura.version}.jar
 cobertura.lib=${cobertura.home}/lib
-cobertura.loc=${base-sonatype-snapshots}/net/sourceforge/cobertura/cobertura/${cobertura.version}/cobertura-2.1.0-20141121.083251-1-bin.tar.gz
+cobertura.loc=${base-sf.loc}/cobertura/cobertura-2.0.3-bin.tar.gz
 
 # ----- JVM settings for unit tests
 java.net.preferIPv4Stack=false
diff --git a/build.xml b/build.xml
index 0a0d298..1f65a26 100644
--- a/build.xml
+++ b/build.xml
@@ -77,9 +77,9 @@
   <!-- build output directory for jdbc-pool -->
   <property name="tomcat.pool"           value="${tomcat.output}/jdbc-pool"/>
 
-  <!-- Servlet 4.0 spec requires 1.8+ -->
-  <property name="compile.source" value="1.8"/>
-  <property name="compile.target" value="1.8"/>
+  <!-- Servlet 3.1 spec requires 1.7+ -->
+  <property name="compile.source" value="1.7"/>
+  <property name="compile.target" value="1.7"/>
 
   <!-- Locations to create the JAR artifacts -->
   <!-- Standard JARs -->
@@ -1339,11 +1339,17 @@
   <property name="junit.formatter.extension" value=".txt" />
 
   <target name="test" description="Runs the JUnit test cases"
-          depends="test-nio,test-nio2,test-apr,cobertura-report" >
+          depends="test-bio,test-nio,test-nio2,test-apr,cobertura-report" >
     <fail if="test.result.error" message='Some tests completed with an Error. See ${tomcat.build}/logs for details, search for "FAILED".' />
     <fail if="test.result.failure" message='Some tests completed with a Failure. See ${tomcat.build}/logs for details, search for "FAILED".' />
   </target>
 
+  <target name="test-bio" description="Runs the JUnit test cases for BIO. Does not stop on errors."
+          depends="test-compile,deploy,cobertura-instrument,test-openssl-exists" if="${execute.test.bio}">
+    <runtests protocol="org.apache.coyote.http11.Http11Protocol"
+              extension=".BIO" />
+  </target>
+
   <target name="test-nio" description="Runs the JUnit test cases for NIO. Does not stop on errors."
           depends="test-compile,deploy,cobertura-instrument,test-openssl-exists" if="${execute.test.nio}">
     <runtests protocol="org.apache.coyote.http11.Http11NioProtocol"
@@ -1461,7 +1467,6 @@
         <exclude name="lib/**/jetty*.jar" />
         <exclude name="lib/**/servlet-api*.jar" />
       </fileset>
-      <pathelement path="res/cobertura"/>
     </path>
 
     <taskdef classpathref="cobertura.classpath" resource="tasks.properties" />
@@ -1495,7 +1500,7 @@
   </target>
 
   <target name="cobertura-report" if="${test.cobertura}"
-          depends="test-nio,test-nio2,test-apr"
+          depends="test-bio,test-nio,test-nio2,test-apr"
           description="Creates report from gathered Cobertura results">
 
     <cobertura-report srcdir="${basedir}/java" destdir="${cobertura.out}"
diff --git a/conf/catalina.policy b/conf/catalina.policy
index 3166def..a48a027 100644
--- a/conf/catalina.policy
+++ b/conf/catalina.policy
@@ -186,6 +186,9 @@
     permission java.util.PropertyPermission
      "org.apache.tomcat.util.http.ServerCookie.FWD_SLASH_IS_SEPARATOR", "read";
 
+    // Applications using Comet need to be able to access this package
+    permission java.lang.RuntimePermission "accessClassInPackage.org.apache.catalina.comet";
+
     // Applications using WebSocket need to be able to access these packages
     permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat.websocket";
     permission java.lang.RuntimePermission "accessClassInPackage.org.apache.tomcat.websocket.server";
diff --git a/conf/context.xml b/conf/context.xml
index 966a2a9..98727cb 100644
--- a/conf/context.xml
+++ b/conf/context.xml
@@ -27,4 +27,10 @@
     <!--
     <Manager pathname="" />
     -->
+
+    <!-- Uncomment this to enable Comet connection tacking (provides events
+         on session expiration as well as webapp lifecycle) -->
+    <!--
+    <Valve className="org.apache.catalina.valves.CometConnectionManagerValve" />
+    -->
 </Context>
diff --git a/conf/server.xml b/conf/server.xml
index 3c9e71d..eb7dad7 100644
--- a/conf/server.xml
+++ b/conf/server.xml
@@ -61,7 +61,7 @@
 
     <!-- A "Connector" represents an endpoint by which requests are received
          and responses are returned. Documentation at :
-         Java HTTP Connector: /docs/config/http.html
+         Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)
          Java AJP  Connector: /docs/config/ajp.html
          APR (HTTP/AJP) Connector: /docs/apr.html
          Define a non-SSL/TLS HTTP/1.1 Connector on port 8080
diff --git a/conf/tomcat-users.xsd b/conf/tomcat-users.xsd
index 67a1d5f..4432065 100644
--- a/conf/tomcat-users.xsd
+++ b/conf/tomcat-users.xsd
@@ -1,20 +1,4 @@
 <?xml version="1.0" encoding="UTF-8" ?>
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You 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.
--->
 <xs:schema xmlns="http://www.w3.org/2001/XMLSchema"
            targetNamespace="http://tomcat.apache.org/xml"
            xmlns:users="http://tomcat.apache.org/xml"
diff --git a/conf/web.xml b/conf/web.xml
index fda3050..8a501af 100644
--- a/conf/web.xml
+++ b/conf/web.xml
@@ -137,9 +137,9 @@
   <!--                       pages.  See the jasper documentation for more  -->
   <!--                       information.                                   -->
   <!--                                                                      -->
-  <!--   compilerSourceVM    Compiler source VM. [1.8]                      -->
+  <!--   compilerSourceVM    Compiler source VM. [1.7]                      -->
   <!--                                                                      -->
-  <!--   compilerTargetVM    Compiler target VM. [1.8]                      -->
+  <!--   compilerTargetVM    Compiler target VM. [1.7]                      -->
   <!--                                                                      -->
   <!--   development         Is Jasper used in development mode? If true,   -->
   <!--                       the frequency at which JSPs are checked for    -->
diff --git a/java/javax/el/CompositeELResolver.java b/java/javax/el/CompositeELResolver.java
index 5f5458d..d1bc7bd 100644
--- a/java/javax/el/CompositeELResolver.java
+++ b/java/javax/el/CompositeELResolver.java
@@ -14,6 +14,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
 package javax.el;
 
 import java.beans.FeatureDescriptor;
@@ -26,7 +27,8 @@
     static {
         Class<?> clazz = null;
         try {
-            clazz = Class.forName("javax.servlet.jsp.el.ScopedAttributeELResolver");
+            clazz =
+                Class.forName("javax.servlet.jsp.el.ScopedAttributeELResolver");
         } catch (ClassNotFoundException e) {
             // Ignore. This is expected if using the EL stand-alone
         }
@@ -59,8 +61,9 @@
     public Object getValue(ELContext context, Object base, Object property) {
         context.setPropertyResolved(false);
         int sz = this.size;
+        Object result = null;
         for (int i = 0; i < sz; i++) {
-            Object result = this.resolvers[i].getValue(context, base, property);
+            result = this.resolvers[i].getValue(context, base, property);
             if (context.isPropertyResolved()) {
                 return result;
             }
@@ -76,8 +79,10 @@
             Class<?>[] paramTypes, Object[] params) {
         context.setPropertyResolved(false);
         int sz = this.size;
+        Object obj;
         for (int i = 0; i < sz; i++) {
-            Object obj = this.resolvers[i].invoke(context, base, method, paramTypes, params);
+            obj = this.resolvers[i].invoke(context, base, method, paramTypes,
+                    params);
             if (context.isPropertyResolved()) {
                 return obj;
             }
@@ -89,15 +94,18 @@
     public Class<?> getType(ELContext context, Object base, Object property) {
         context.setPropertyResolved(false);
         int sz = this.size;
+        Class<?> type;
         for (int i = 0; i < sz; i++) {
-            Class<?> type = this.resolvers[i].getType(context, base, property);
+            type = this.resolvers[i].getType(context, base, property);
             if (context.isPropertyResolved()) {
                 if (SCOPED_ATTRIBUTE_EL_RESOLVER != null &&
-                        SCOPED_ATTRIBUTE_EL_RESOLVER.isAssignableFrom(resolvers[i].getClass())) {
+                        SCOPED_ATTRIBUTE_EL_RESOLVER.isAssignableFrom(
+                                resolvers[i].getClass())) {
                     // Special case since
                     // javax.servlet.jsp.el.ScopedAttributeELResolver will
                     // always return Object.class for type
-                    Object value = resolvers[i].getValue(context, base, property);
+                    Object value =
+                        resolvers[i].getValue(context, base, property);
                     if (value != null) {
                         return value.getClass();
                     }
@@ -109,7 +117,8 @@
     }
 
     @Override
-    public void setValue(ELContext context, Object base, Object property, Object value) {
+    public void setValue(ELContext context, Object base, Object property,
+            Object value) {
         context.setPropertyResolved(false);
         int sz = this.size;
         for (int i = 0; i < sz; i++) {
@@ -124,8 +133,9 @@
     public boolean isReadOnly(ELContext context, Object base, Object property) {
         context.setPropertyResolved(false);
         int sz = this.size;
+        boolean readOnly = false;
         for (int i = 0; i < sz; i++) {
-            boolean readOnly = this.resolvers[i].isReadOnly(context, base, property);
+            readOnly = this.resolvers[i].isReadOnly(context, base, property);
             if (context.isPropertyResolved()) {
                 return readOnly;
             }
@@ -140,11 +150,12 @@
 
     @Override
     public Class<?> getCommonPropertyType(ELContext context, Object base) {
-        Class<?> commonType = null;
         int sz = this.size;
+        Class<?> commonType = null, type = null;
         for (int i = 0; i < sz; i++) {
-            Class<?> type = this.resolvers[i].getCommonPropertyType(context, base);
-            if (type != null && (commonType == null || commonType.isAssignableFrom(type))) {
+            type = this.resolvers[i].getCommonPropertyType(context, base);
+            if (type != null &&
+                    (commonType == null || commonType.isAssignableFrom(type))) {
                 commonType = type;
             }
         }
@@ -155,8 +166,9 @@
     public Object convertToType(ELContext context, Object obj, Class<?> type) {
         context.setPropertyResolved(false);
         int sz = this.size;
+        Object result = null;
         for (int i = 0; i < sz; i++) {
-            Object result = this.resolvers[i].convertToType(context, obj, type);
+            result = this.resolvers[i].convertToType(context, obj, type);
             if (context.isPropertyResolved()) {
                 return result;
             }
@@ -180,7 +192,8 @@
 
         private FeatureDescriptor next;
 
-        public FeatureIterator(ELContext context, Object base, ELResolver[] resolvers, int size) {
+        public FeatureIterator(ELContext context, Object base,
+                ELResolver[] resolvers, int size) {
             this.context = context;
             this.base = base;
             this.resolvers = resolvers;
@@ -192,7 +205,8 @@
 
         private void guaranteeIterator() {
             while (this.itr == null && this.idx < this.size) {
-                this.itr = this.resolvers[this.idx].getFeatureDescriptors(this.context, this.base);
+                this.itr = this.resolvers[this.idx].getFeatureDescriptors(
+                        this.context, this.base);
                 this.idx++;
             }
         }
@@ -201,7 +215,7 @@
         public boolean hasNext() {
             if (this.next != null)
                 return true;
-            if (this.itr != null) {
+            if (this.itr != null){
                 while (this.next == null && itr.hasNext()) {
                     this.next = itr.next();
                 }
@@ -217,9 +231,8 @@
 
         @Override
         public FeatureDescriptor next() {
-            if (!hasNext()) {
+            if (!hasNext())
                 throw new NoSuchElementException();
-            }
             FeatureDescriptor result = this.next;
             this.next = null;
             return result;
@@ -231,4 +244,5 @@
             throw new UnsupportedOperationException();
         }
     }
+
 }
diff --git a/java/javax/el/ExpressionFactory.java b/java/javax/el/ExpressionFactory.java
index 664f623..3b001a8 100644
--- a/java/javax/el/ExpressionFactory.java
+++ b/java/javax/el/ExpressionFactory.java
@@ -325,7 +325,7 @@
         }
 
         public void setFactoryClass(Class<?> clazz) {
-            ref = new WeakReference<>(clazz);
+            ref = new WeakReference<Class<?>>(clazz);
         }
     }
 
diff --git a/java/javax/el/ImportHandler.java b/java/javax/el/ImportHandler.java
index 2c7d7a4..1cba6bf 100644
--- a/java/javax/el/ImportHandler.java
+++ b/java/javax/el/ImportHandler.java
@@ -31,7 +31,7 @@
 public class ImportHandler {
 
     private List<String> packageNames = new ArrayList<>();
-    private Map<String,String> classNames = new ConcurrentHashMap<>();
+    private ConcurrentHashMap<String,String> classNames = new ConcurrentHashMap<>();
     private Map<String,Class<?>> clazzes = new ConcurrentHashMap<>();
     private Map<String,Class<?>> statics = new ConcurrentHashMap<>();
 
diff --git a/java/org/apache/catalina/Globals.java b/java/org/apache/catalina/Globals.java
index 52cc153..30eea17 100644
--- a/java/org/apache/catalina/Globals.java
+++ b/java/org/apache/catalina/Globals.java
@@ -138,6 +138,41 @@
 
     /**
      * The request attribute that is set to the value of {@code Boolean.TRUE}
+     * if connector processing this request supports Comet API.
+     * Duplicated here for neater code in the catalina packages.
+     */
+    public static final String COMET_SUPPORTED_ATTR =
+        org.apache.coyote.Constants.COMET_SUPPORTED_ATTR;
+
+
+    /**
+     * The request attribute that is set to the value of {@code Boolean.TRUE}
+     * if connector processing this request supports setting
+     * per-connection request timeout through Comet API.
+     *
+     * @see org.apache.catalina.comet.CometEvent#setTimeout(int)
+     *
+     * Duplicated here for neater code in the catalina packages.
+     */
+    public static final String COMET_TIMEOUT_SUPPORTED_ATTR =
+            org.apache.coyote.Constants.COMET_TIMEOUT_SUPPORTED_ATTR;
+
+
+    /**
+     * The request attribute that can be set to a value of type
+     * {@code java.lang.Integer} to specify per-connection request
+     * timeout for Comet API. The value is in milliseconds.
+     *
+     * @see org.apache.catalina.comet.CometEvent#setTimeout(int)
+     *
+     * Duplicated here for neater code in the catalina packages.
+     */
+    public static final String COMET_TIMEOUT_ATTR =
+        org.apache.coyote.Constants.COMET_TIMEOUT_ATTR;
+
+
+    /**
+     * The request attribute that is set to the value of {@code Boolean.TRUE}
      * if connector processing this request supports use of sendfile.
      *
      * Duplicated here for neater code in the catalina packages.
diff --git a/java/org/apache/catalina/Manager.java b/java/org/apache/catalina/Manager.java
index 9d00049..a2e204d 100644
--- a/java/org/apache/catalina/Manager.java
+++ b/java/org/apache/catalina/Manager.java
@@ -14,11 +14,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+
 package org.apache.catalina;
 
+
 import java.beans.PropertyChangeListener;
 import java.io.IOException;
 
+
 /**
  * A <b>Manager</b> manages the pool of Sessions that are associated with a
  * particular Container.  Different Manager implementations may support
@@ -39,12 +43,34 @@
  */
 public interface Manager {
 
+
     // ------------------------------------------------------------- Properties
 
+
     /**
-     * Get the Context with which this Manager is associated.
+     * Return the Container with which this Manager is associated.
      *
-     * @return The associated Context
+     * @deprecated Use {@link #getContext()}. This method will be removed in
+     *             Tomcat 9 onwards.
+     */
+    @Deprecated
+    public Container getContainer();
+
+
+    /**
+     * Set the Container with which this Manager is associated.
+     *
+     * @param container The newly associated Container
+     *
+     * @deprecated Use {@link #setContext(Context)}. This method will be removed in
+     *             Tomcat 9 onwards.
+     */
+    @Deprecated
+    public void setContainer(Container container);
+
+
+    /**
+     * Return the Context with which this Manager is associated.
      */
     public Context getContext();
 
@@ -58,18 +84,16 @@
 
 
     /**
-     * Is this Manager marked as using distributable sessions?
-     *
-     * @return {@code true} if this manager is marked as distributable otherwise
-     *         {@code false}
+     * Return the distributable flag for the sessions supported by
+     * this Manager.
      */
     public boolean getDistributable();
 
 
     /**
-     * Configure whether this manager uses distributable sessions. If this flag
-     * is set, all user data objects added to sessions associated with this
-     * manager must implement Serializable.
+     * Set the distributable flag for the sessions supported by this
+     * Manager.  If this flag is set, all user data objects added to
+     * sessions associated with this manager must implement Serializable.
      *
      * @param distributable The new distributable flag
      */
@@ -77,10 +101,8 @@
 
 
     /**
-     * Get the default time in seconds before a session managed by this manager
-     * will be considered inactive.
-     *
-     * @return The default maximum inactive interval in seconds
+     * Return the default maximum inactive interval (in seconds)
+     * for Sessions created by this Manager.
      */
     public int getMaxInactiveInterval();
 
@@ -95,7 +117,7 @@
 
 
     /**
-     * @return the session id generator
+     * return the session id generator
      */
     public SessionIdGenerator getSessionIdGenerator();
 
@@ -109,6 +131,32 @@
 
 
     /**
+     * Gets the session id length (in bytes) of Sessions created by
+     * this Manager.
+     *
+     * @deprecated Use {@link SessionIdGenerator#getSessionIdLength()}.
+     *             This method will be removed in Tomcat 9 onwards.
+     *
+     * @return The session id length
+     */
+    @Deprecated
+    public int getSessionIdLength();
+
+
+    /**
+     * Sets the session id length (in bytes) for Sessions created by this
+     * Manager.
+     *
+     * @deprecated Use {@link SessionIdGenerator#setSessionIdLength(int)}.
+     *             This method will be removed in Tomcat 9 onwards.
+     *
+     * @param idLength The session id length
+     */
+    @Deprecated
+    public void setSessionIdLength(int idLength);
+
+
+    /**
      * Returns the total number of sessions created by this manager.
      *
      * @return Total number of sessions created by this manager.
@@ -223,10 +271,9 @@
      * @return  The current rate (in sessions per minute) of session expiration
      */
     public int getSessionExpireRate();
-
-
     // --------------------------------------------------------- Public Methods
 
+
     /**
      * Add this Session to the set of active Sessions for this Manager.
      *
@@ -265,8 +312,6 @@
      * Get a session from the recycled ones or create a new empty one.
      * The PersistentManager manager does not need to create session data
      * because it reads it from the Store.
-     *
-     * @return An empty Session object
      */
     public Session createEmptySession();
 
@@ -284,9 +329,6 @@
      *  method of the returned session.
      * @exception IllegalStateException if a new session cannot be
      *  instantiated for any reason
-     *
-     * @return An empty Session object with the given ID or a newly created
-     *         session ID if none was specified
      */
     public Session createSession(String sessionId);
 
@@ -301,9 +343,6 @@
      *  instantiated for any reason
      * @exception IOException if an input/output error occurs while
      *  processing this request
-     *
-     * @return the request session or {@code null} if a session with the
-     *         requested ID could not be found
      */
     public Session findSession(String id) throws IOException;
 
@@ -311,8 +350,6 @@
     /**
      * Return the set of active Sessions associated with this Manager.
      * If this Manager has no active Sessions, a zero-length array is returned.
-     *
-     * @return All the currently active sessions managed by this manager
      */
     public Session[] findSessions();
 
@@ -363,11 +400,11 @@
      */
     public void unload() throws IOException;
 
+     /**
+      * This method will be invoked by the context/container on a periodic
+      * basis and allows the manager to implement
+      * a method that executes periodic tasks, such as expiring sessions etc.
+      */
+     public void backgroundProcess();
 
-    /**
-     * This method will be invoked by the context/container on a periodic
-     * basis and allows the manager to implement
-     * a method that executes periodic tasks, such as expiring sessions etc.
-     */
-    public void backgroundProcess();
 }
diff --git a/java/org/apache/catalina/Valve.java b/java/org/apache/catalina/Valve.java
index fd05a7d..ac0af85 100644
--- a/java/org/apache/catalina/Valve.java
+++ b/java/org/apache/catalina/Valve.java
@@ -14,15 +14,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+
 package org.apache.catalina;
 
+
 import java.io.IOException;
 
 import javax.servlet.ServletException;
 
+import org.apache.catalina.comet.CometEvent;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 
+
 /**
  * <p>A <b>Valve</b> is a request processing component associated with a
  * particular Container.  A series of Valves are generally associated with
@@ -118,5 +123,22 @@
         throws IOException, ServletException;
 
 
+    /**
+     * Process a Comet event.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException;
+
+
     public boolean isAsyncSupported();
+
+
 }
diff --git a/java/org/apache/catalina/authenticator/AuthenticatorBase.java b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
index a309bf1..a3e4904 100644
--- a/java/org/apache/catalina/authenticator/AuthenticatorBase.java
+++ b/java/org/apache/catalina/authenticator/AuthenticatorBase.java
@@ -70,20 +70,19 @@
  *
  * @author Craig R. McClanahan
  */
-public abstract class AuthenticatorBase extends ValveBase implements Authenticator {
+public abstract class AuthenticatorBase extends ValveBase
+        implements Authenticator {
 
     private static final Log log = LogFactory.getLog(AuthenticatorBase.class);
 
-    /**
-     * "Expires" header always set to Date(1), so generate once only
-     */
-    private static final String DATE_ONE = (new SimpleDateFormat(
-            FastHttpDateFormat.RFC1123_DATE, Locale.US)).format(new Date(1));
 
-    /**
-     * The string manager for this package.
-     */
-    protected static final StringManager sm = StringManager.getManager(AuthenticatorBase.class);
+    //------------------------------------------------------ Constructor
+    public AuthenticatorBase() {
+        super(true);
+    }
+
+    // ----------------------------------------------------- Instance Variables
+
 
     /**
      * Authentication header
@@ -95,37 +94,6 @@
      */
     protected static final String REALM_NAME = "Authentication required";
 
-
-    protected static String getRealmName(Context context) {
-        if (context == null) {
-            // Very unlikely
-            return REALM_NAME;
-        }
-
-        LoginConfig config = context.getLoginConfig();
-        if (config == null) {
-            return REALM_NAME;
-        }
-
-        String result = config.getRealmName();
-        if (result == null) {
-            return REALM_NAME;
-        }
-
-        return result;
-    }
-
-
-
-    //------------------------------------------------------ Constructor
-
-    public AuthenticatorBase() {
-        super(true);
-    }
-
-
-    // ----------------------------------------------------- Instance Variables
-
     /**
      * Should a session always be used once a user is authenticated? This may
      * offer some performance benefits since the session can then be used to
@@ -203,14 +171,50 @@
     protected SessionIdGeneratorBase sessionIdGenerator = null;
 
     /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
      * The SingleSignOn implementation in our request processing chain,
      * if there is one.
      */
     protected SingleSignOn sso = null;
 
 
+    /**
+     * "Expires" header always set to Date(1), so generate once only
+     */
+    private static final String DATE_ONE =
+        (new SimpleDateFormat(FastHttpDateFormat.RFC1123_DATE,
+                              Locale.US)).format(new Date(1));
+
+
+    protected static String getRealmName(Context context) {
+        if (context == null) {
+            // Very unlikely
+            return REALM_NAME;
+        }
+
+        LoginConfig config = context.getLoginConfig();
+        if (config == null) {
+            return REALM_NAME;
+        }
+
+        String result = config.getRealmName();
+        if (result == null) {
+            return REALM_NAME;
+        }
+
+        return result;
+    }
+
+
     // ------------------------------------------------------------- Properties
 
+
     public boolean getAlwaysUseSession() {
         return alwaysUseSession;
     }
@@ -225,7 +229,9 @@
      * Return the cache authenticated Principals flag.
      */
     public boolean getCache() {
-        return this.cache;
+
+        return (this.cache);
+
     }
 
 
@@ -235,7 +241,9 @@
      * @param cache The new cache flag
      */
     public void setCache(boolean cache) {
+
         this.cache = cache;
+
     }
 
 
@@ -244,7 +252,9 @@
      */
     @Override
     public Container getContainer() {
-        return this.context;
+
+        return (this.context);
+
     }
 
 
@@ -391,6 +401,7 @@
 
     // --------------------------------------------------------- Public Methods
 
+
     /**
      * Enforce the security restrictions in the web application deployment
      * descriptor of our associated Context.
diff --git a/java/org/apache/catalina/authenticator/Constants.java b/java/org/apache/catalina/authenticator/Constants.java
index 61f3fb7..1f35b52 100644
--- a/java/org/apache/catalina/authenticator/Constants.java
+++ b/java/org/apache/catalina/authenticator/Constants.java
@@ -20,6 +20,9 @@
 
 
 public class Constants {
+
+    public static final String Package = "org.apache.catalina.authenticator";
+
     // Authentication methods for login configuration
     // Servlet spec schemes are defined in HttpServletRequest
     // Vendor specific schemes
diff --git a/java/org/apache/catalina/authenticator/SingleSignOn.java b/java/org/apache/catalina/authenticator/SingleSignOn.java
index 1a915ce..2f725e3 100644
--- a/java/org/apache/catalina/authenticator/SingleSignOn.java
+++ b/java/org/apache/catalina/authenticator/SingleSignOn.java
@@ -59,7 +59,7 @@
  */
 public class SingleSignOn extends ValveBase {
 
-    private static final StringManager sm = StringManager.getManager(SingleSignOn.class);
+    private static final StringManager sm = StringManager.getManager(SingleSignOn.class.getName());
 
     /* The engine at the top of the container hierarchy in which this SSO Valve
      * has been placed. It is used to get back to a session object from a
diff --git a/java/org/apache/catalina/comet/CometEvent.java b/java/org/apache/catalina/comet/CometEvent.java
new file mode 100644
index 0000000..4cadc6c
--- /dev/null
+++ b/java/org/apache/catalina/comet/CometEvent.java
@@ -0,0 +1,146 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.comet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * The CometEvent interface.
+ *
+ * @author Remy Maucherat
+ */
+public interface CometEvent {
+
+    /**
+     * Enumeration describing the major events that the container can invoke
+     * the CometProcessors event() method with.<br>
+     * BEGIN - will be called at the beginning
+     *  of the processing of the connection. It can be used to initialize any relevant
+     *  fields using the request and response objects. Between the end of the processing
+     *  of this event, and the beginning of the processing of the end or error events,
+     *  it is possible to use the response object to write data on the open connection.
+     *  Note that the response object and dependent OutputStream and Writer are still
+     *  not synchronized, so when they are accessed by multiple threads,
+     *  synchronization is mandatory. After processing the initial event, the request
+     *  is considered to be committed.<br>
+     * READ - This indicates that input data is available, and that one read can be made
+     *  without blocking. The available and ready methods of the InputStream or
+     *  Reader may be used to determine if there is a risk of blocking: the servlet
+     *  should read while data is reported available. When encountering a read error,
+     *  the servlet should report it by propagating the exception properly. Throwing
+     *  an exception will cause the error event to be invoked, and the connection
+     *  will be closed.
+     *  Alternately, it is also possible to catch any exception, perform clean up
+     *  on any data structure the servlet may be using, and using the close method
+     *  of the event. It is not allowed to attempt reading data from the request
+     *  object outside of the execution of this method.<br>
+     * END - End may be called to end the processing of the request. Fields that have
+     *  been initialized in the begin method should be reset. After this event has
+     *  been processed, the request and response objects, as well as all their dependent
+     *  objects will be recycled and used to process other requests. End will also be
+     *  called when data is available and the end of file is reached on the request input
+     *  (this usually indicates the client has pipelined a request).<br>
+     * ERROR - Error will be called by the container in the case where an IO exception
+     *  or a similar unrecoverable error occurs on the connection. Fields that have
+     *  been initialized in the begin method should be reset. After this event has
+     *  been processed, the request and response objects, as well as all their dependent
+     *  objects will be recycled and used to process other requests.
+     */
+    public enum EventType {BEGIN, READ, END, ERROR}
+
+
+    /**
+     * Event details.<br>
+     * TIMEOUT - the connection timed out (sub type of ERROR); note that this ERROR type is not fatal, and
+     *   the connection will not be closed unless the servlet uses the close method of the event<br>
+     * CLIENT_DISCONNECT - the client connection was closed (sub type of ERROR)<br>
+     * IOEXCEPTION - an IO exception occurred, such as invalid content, for example, an invalid chunk block (sub type of ERROR)<br>
+     * WEBAPP_RELOAD - the webapplication is being reloaded (sub type of END)<br>
+     * SERVER_SHUTDOWN - the server is shutting down (sub type of END)<br>
+     * SESSION_END - the servlet ended the session (sub type of END)
+     */
+    public enum EventSubType { TIMEOUT, CLIENT_DISCONNECT, IOEXCEPTION, WEBAPP_RELOAD, SERVER_SHUTDOWN, SESSION_END }
+
+
+    /**
+     * Returns the HttpServletRequest.
+     *
+     * @return HttpServletRequest
+     */
+    public HttpServletRequest getHttpServletRequest();
+
+    /**
+     * Returns the HttpServletResponse.
+     *
+     * @return HttpServletResponse
+     */
+    public HttpServletResponse getHttpServletResponse();
+
+    /**
+     * Returns the event type.
+     *
+     * @return EventType
+     */
+    public EventType getEventType();
+
+    /**
+     * Returns the sub type of this event.
+     *
+     * @return EventSubType
+     */
+    public EventSubType getEventSubType();
+
+    /**
+     * Ends the Comet session. This signals to the container that
+     * the container wants to end the comet session. This will send back to the
+     * client a notice that the server has no more data to send as part of this
+     * request. The servlet should perform any needed cleanup as if it had received
+     * an END or ERROR event.
+     *
+     * @throws IOException if an IO exception occurs
+     */
+    public void close() throws IOException;
+
+    /**
+     * Sets the timeout for this Comet connection. Please NOTE, that the implementation
+     * of a per connection timeout is OPTIONAL and MAY NOT be implemented.<br>
+     * This method sets the timeout in milliseconds of idle time on the connection.
+     * The timeout is reset every time data is received from the connection or data is flushed
+     * using <code>response.flushBuffer()</code>. If a timeout occurs, the
+     * <code>error(HttpServletRequest, HttpServletResponse)</code> method is invoked. The
+     * web application SHOULD NOT attempt to reuse the request and response objects after a timeout
+     * as the <code>error(HttpServletRequest, HttpServletResponse)</code> method indicates.<br>
+     * This method should not be called asynchronously, as that will have no effect.
+     *
+     * @param timeout The timeout in milliseconds for this connection, must be a positive value, larger than 0
+     * @throws IOException An IOException may be thrown to indicate an IO error,
+     *         or that the EOF has been reached on the connection
+     * @throws ServletException An exception has occurred, as specified by the root
+     *         cause
+     * @throws UnsupportedOperationException if per connection timeout is not supported, either at all or at this phase
+     *         of the invocation.
+     */
+    public void setTimeout(int timeout)
+        throws IOException, ServletException, UnsupportedOperationException;
+
+}
diff --git a/java/org/apache/catalina/comet/CometFilter.java b/java/org/apache/catalina/comet/CometFilter.java
new file mode 100644
index 0000000..ffb0d1c
--- /dev/null
+++ b/java/org/apache/catalina/comet/CometFilter.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.comet;
+
+import java.io.IOException;
+
+import javax.servlet.Filter;
+import javax.servlet.ServletException;
+
+/**
+ * A Comet filter, similar to regular filters, performs filtering tasks on either
+ * the request to a resource (a Comet servlet), or on the response from a resource, or both.
+ * <br><br>
+ * Filters perform filtering in the <code>doFilterEvent</code> method. Every Filter has access to
+ * a FilterConfig object from which it can obtain its initialization parameters, a
+ * reference to the ServletContext which it can use, for example, to load resources
+ * needed for filtering tasks.
+ * <p>
+ * Filters are configured in the deployment descriptor of a web application
+ * <p>
+ * Examples that have been identified for this design are<br>
+ * 1) Authentication Filters <br>
+ * 2) Logging and Auditing Filters <br>
+ * 3) Image conversion Filters <br>
+ * 4) Data compression Filters <br>
+ * 5) Encryption Filters <br>
+ * 6) Tokenizing Filters <br>
+ * 7) Filters that trigger resource access events <br>
+ * 8) XSL/T filters <br>
+ * 9) Mime-type chain Filter <br>
+ * <br>
+ *
+ * @author Remy Maucherat
+ */
+public interface CometFilter extends Filter {
+
+
+    /**
+     * The <code>doFilterEvent</code> method of the CometFilter is called by the container
+     * each time a request/response pair is passed through the chain due
+     * to a client event for a resource at the end of the chain. The CometFilterChain passed in to this
+     * method allows the Filter to pass on the event to the next entity in the
+     * chain.<p>
+     * A typical implementation of this method would follow the following pattern:- <br>
+     * 1. Examine the request<br>
+     * 2. Optionally wrap the request object contained in the event with a custom implementation to
+     * filter content or headers for input filtering and pass a CometEvent instance containing
+     * the wrapped request to the next filter<br>
+     * 3. Optionally wrap the response object contained in the event with a custom implementation to
+     * filter content or headers for output filtering and pass a CometEvent instance containing
+     * the wrapped request to the next filter<br>
+     * 4. a) <strong>Either</strong> invoke the next entity in the chain using the CometFilterChain object (<code>chain.doFilterEvent()</code>), <br>
+     * 4. b) <strong>or</strong> not pass on the request/response pair to the next entity in the filter chain to block the event processing<br>
+     * 5. Directly set fields on the response after invocation of the next entity in the filter chain.
+     *
+     * @param event the event that is being processed. Another event may be passed along the chain.
+     * @param chain
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+        throws IOException, ServletException;
+
+
+}
diff --git a/java/org/apache/catalina/comet/CometFilterChain.java b/java/org/apache/catalina/comet/CometFilterChain.java
new file mode 100644
index 0000000..68ff5f1
--- /dev/null
+++ b/java/org/apache/catalina/comet/CometFilterChain.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.comet;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+/**
+ * A CometFilterChain is an object provided by the servlet container to the developer
+ * giving a view into the invocation chain of a filtered event for a resource. Filters
+ * use the CometFilterChain to invoke the next filter in the chain, or if the calling filter
+ * is the last filter in the chain, to invoke the resource at the end of the chain.
+ *
+ * @author Remy Maucherat
+ */
+public interface CometFilterChain {
+
+
+    /**
+     * Causes the next filter in the chain to be invoked, or if the calling filter is the last filter
+     * in the chain, causes the resource at the end of the chain to be invoked.
+     *
+     * @param event the event to pass along the chain.
+     */
+    public void doFilterEvent(CometEvent event) throws IOException, ServletException;
+
+
+}
diff --git a/java/org/apache/catalina/comet/CometProcessor.java b/java/org/apache/catalina/comet/CometProcessor.java
new file mode 100644
index 0000000..4e22f8d
--- /dev/null
+++ b/java/org/apache/catalina/comet/CometProcessor.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.comet;
+
+import java.io.IOException;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+/**
+ * This interface should be implemented by servlets which would like to handle
+ * asynchronous IO, receiving events when data is available for reading, and
+ * being able to output data without the need for being invoked by the container.
+ * Note: When this interface is implemented, the service method of the servlet will
+ * never be called, and will be replaced with a begin event.
+ */
+public interface CometProcessor extends Servlet{
+
+    /**
+     * Process the given Comet event.
+     *
+     * @param event The Comet event that will be processed
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void event(CometEvent event)
+        throws IOException, ServletException;
+
+}
diff --git a/java/org/apache/catalina/connector/CometEventImpl.java b/java/org/apache/catalina/connector/CometEventImpl.java
new file mode 100644
index 0000000..97c54b1
--- /dev/null
+++ b/java/org/apache/catalina/connector/CometEventImpl.java
@@ -0,0 +1,149 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.connector;
+
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.Globals;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.util.res.StringManager;
+
+public class CometEventImpl implements CometEvent {
+
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    public CometEventImpl(Request request, Response response) {
+        this.request = request;
+        this.response = response;
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Associated request.
+     */
+    protected Request request = null;
+
+
+    /**
+     * Associated response.
+     */
+    protected Response response = null;
+
+
+    /**
+     * Event type.
+     */
+    protected EventType eventType = EventType.BEGIN;
+
+
+    /**
+     * Event sub type.
+     */
+    protected EventSubType eventSubType = null;
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Clear the event.
+     */
+    public void clear() {
+        request = null;
+        response = null;
+    }
+
+    public void setEventType(EventType eventType) {
+        this.eventType = eventType;
+    }
+
+    public void setEventSubType(EventSubType eventSubType) {
+        this.eventSubType = eventSubType;
+    }
+
+    @Override
+    public void close() throws IOException {
+        if (request == null) {
+            throw new IllegalStateException(sm.getString("cometEvent.nullRequest"));
+        }
+        request.finishRequest();
+        response.finishResponse();
+        if (request.isComet()) {
+            request.cometClose();
+        }
+    }
+
+    @Override
+    public EventSubType getEventSubType() {
+        return eventSubType;
+    }
+
+    @Override
+    public EventType getEventType() {
+        return eventType;
+    }
+
+    @Override
+    public HttpServletRequest getHttpServletRequest() {
+        return request.getRequest();
+    }
+
+    @Override
+    public HttpServletResponse getHttpServletResponse() {
+        return response.getResponse();
+    }
+
+    @Override
+    public void setTimeout(int timeout) throws IOException, ServletException,
+            UnsupportedOperationException {
+        if (request.getAttribute(Globals.COMET_TIMEOUT_SUPPORTED_ATTR) ==
+                Boolean.TRUE) {
+            request.setAttribute(Globals.COMET_TIMEOUT_ATTR,
+                    Integer.valueOf(timeout));
+            if (request.isComet()) {
+                request.setCometTimeout(timeout);
+            }
+        } else {
+            throw new UnsupportedOperationException();
+        }
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder buf = new StringBuilder();
+        buf.append(super.toString());
+        buf.append("[EventType:");
+        buf.append(eventType);
+        buf.append(", EventSubType:");
+        buf.append(eventSubType);
+        buf.append("]");
+        return buf.toString();
+    }
+
+}
diff --git a/java/org/apache/catalina/connector/Connector.java b/java/org/apache/catalina/connector/Connector.java
index 46db68f..479f3d2 100644
--- a/java/org/apache/catalina/connector/Connector.java
+++ b/java/org/apache/catalina/connector/Connector.java
@@ -163,7 +163,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(Connector.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     /**
diff --git a/java/org/apache/catalina/connector/Constants.java b/java/org/apache/catalina/connector/Constants.java
new file mode 100644
index 0000000..3130be4
--- /dev/null
+++ b/java/org/apache/catalina/connector/Constants.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.connector;
+
+/**
+ * Static constants for this package.
+ */
+public final class Constants {
+
+    public static final String Package = "org.apache.catalina.connector";
+
+}
diff --git a/java/org/apache/catalina/connector/CoyoteAdapter.java b/java/org/apache/catalina/connector/CoyoteAdapter.java
index f3ea8df..5ea9c98 100644
--- a/java/org/apache/catalina/connector/CoyoteAdapter.java
+++ b/java/org/apache/catalina/connector/CoyoteAdapter.java
@@ -32,6 +32,8 @@
 import org.apache.catalina.Context;
 import org.apache.catalina.Host;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometEvent.EventType;
 import org.apache.catalina.core.AsyncContextImpl;
 import org.apache.catalina.util.ServerInfo;
 import org.apache.catalina.util.SessionConfig;
@@ -118,11 +120,141 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(CoyoteAdapter.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     // -------------------------------------------------------- Adapter Methods
 
+
+    /**
+     * Event method.
+     *
+     * @return false to indicate an error, expected or not
+     */
+    @Override
+    public boolean event(org.apache.coyote.Request req,
+            org.apache.coyote.Response res, SocketStatus status) {
+
+        Request request = (Request) req.getNote(ADAPTER_NOTES);
+        Response response = (Response) res.getNote(ADAPTER_NOTES);
+
+        if (request.getWrapper() == null) {
+            return false;
+        }
+
+        boolean error = false;
+        boolean read = false;
+        try {
+            if (status == SocketStatus.OPEN_READ) {
+                if (response.isClosed()) {
+                    // The event has been closed asynchronously, so call end instead of
+                    // read to cleanup the pipeline
+                    request.getEvent().setEventType(CometEvent.EventType.END);
+                    request.getEvent().setEventSubType(null);
+                } else {
+                    try {
+                        // Fill the read buffer of the servlet layer
+                        if (request.read()) {
+                            read = true;
+                        }
+                    } catch (IOException e) {
+                        error = true;
+                    }
+                    if (read) {
+                        request.getEvent().setEventType(CometEvent.EventType.READ);
+                        request.getEvent().setEventSubType(null);
+                    } else if (error) {
+                        request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                        request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
+                    } else {
+                        request.getEvent().setEventType(CometEvent.EventType.END);
+                        request.getEvent().setEventSubType(null);
+                    }
+                }
+            } else if (status == SocketStatus.DISCONNECT) {
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.CLIENT_DISCONNECT);
+                error = true;
+            } else if (status == SocketStatus.ERROR) {
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
+                error = true;
+            } else if (status == SocketStatus.STOP) {
+                request.getEvent().setEventType(CometEvent.EventType.END);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.SERVER_SHUTDOWN);
+            } else if (status == SocketStatus.TIMEOUT) {
+                if (response.isClosed()) {
+                    // The event has been closed asynchronously, so call end instead of
+                    // read to cleanup the pipeline
+                    request.getEvent().setEventType(CometEvent.EventType.END);
+                    request.getEvent().setEventSubType(null);
+                } else {
+                    request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                    request.getEvent().setEventSubType(CometEvent.EventSubType.TIMEOUT);
+                }
+            }
+
+            req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
+
+            // Calling the container
+            connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+
+            if (!error && !response.isClosed() && (request.getAttribute(
+                    RequestDispatcher.ERROR_EXCEPTION) != null)) {
+                // An unexpected exception occurred while processing the event, so
+                // error should be called
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(null);
+                error = true;
+                connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+            }
+            if (response.isClosed() || !request.isComet()) {
+                if (status==SocketStatus.OPEN_READ &&
+                        request.getEvent().getEventType() != EventType.END) {
+                    //CometEvent.close was called during an event other than END
+                    request.getEvent().setEventType(CometEvent.EventType.END);
+                    request.getEvent().setEventSubType(null);
+                    error = true;
+                    connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+                }
+                res.action(ActionCode.COMET_END, null);
+            } else if (!error && read && request.getAvailable()) {
+                // If this was a read and not all bytes have been read, or if no data
+                // was read from the connector, then it is an error
+                request.getEvent().setEventType(CometEvent.EventType.ERROR);
+                request.getEvent().setEventSubType(CometEvent.EventSubType.IOEXCEPTION);
+                error = true;
+                connector.getService().getContainer().getPipeline().getFirst().event(request, response, request.getEvent());
+            }
+            return (!error);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            if (!(t instanceof IOException)) {
+                log.error(sm.getString("coyoteAdapter.service"), t);
+            }
+            error = true;
+            return false;
+        } finally {
+            req.getRequestProcessor().setWorkerThreadName(null);
+            // Recycle the wrapper request and response
+            if (error || response.isClosed() || !request.isComet()) {
+                if (request.getMappingData().context != null) {
+                    request.getMappingData().context.logAccess(
+                            request, response,
+                            System.currentTimeMillis() - req.getStartTime(),
+                            false);
+                } else {
+                    // Should normally not happen
+                    log(req, res, System.currentTimeMillis() - req.getStartTime());
+                }
+                request.recycle();
+                request.setFilterChain(null);
+                response.recycle();
+            }
+        }
+    }
+
     @Override
     public boolean asyncDispatch(org.apache.coyote.Request req,
             org.apache.coyote.Response res, SocketStatus status) throws Exception {
@@ -133,11 +265,12 @@
             throw new IllegalStateException(
                     "Dispatch may only happen on an existing request.");
         }
+        boolean comet = false;
         boolean success = true;
         AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
         req.getRequestProcessor().setWorkerThreadName(Thread.currentThread().getName());
         try {
-            if (!request.isAsync()) {
+            if (!request.isAsync() && !comet) {
                 // Error or timeout - need to tell listeners the request is over
                 // Have to test this first since state may change while in this
                 // method and this is only required if entering this method in
@@ -260,7 +393,25 @@
                 }
             }
 
-            if (!request.isAsync()) {
+            if (request.isComet()) {
+                if (!response.isClosed() && !response.isError()) {
+                    if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
+                        // Invoke a read event right away if there are available bytes
+                        if (event(req, res, SocketStatus.OPEN_READ)) {
+                            comet = true;
+                            res.action(ActionCode.COMET_BEGIN, null);
+                        }
+                    } else {
+                        comet = true;
+                        res.action(ActionCode.COMET_BEGIN, null);
+                    }
+                } else {
+                    // Clear the filter chain, as otherwise it will not be reset elsewhere
+                    // since this is a Comet request
+                    request.setFilterChain(null);
+                }
+            }
+            if (!request.isAsync() && !comet) {
                 request.finishRequest();
                 response.finishResponse();
                 if (request.getMappingData().context != null) {
@@ -299,7 +450,7 @@
             }
             req.getRequestProcessor().setWorkerThreadName(null);
             // Recycle the wrapper request and response
-            if (!success || !request.isAsync()) {
+            if (!success || (!comet && !request.isAsync())) {
                 request.recycle();
                 response.recycle();
             } else {
@@ -349,6 +500,7 @@
             response.addHeader("X-Powered-By", POWERED_BY);
         }
 
+        boolean comet = false;
         boolean async = false;
 
         try {
@@ -362,6 +514,26 @@
                 request.setAsyncSupported(connector.getService().getContainer().getPipeline().isAsyncSupported());
                 // Calling the container
                 connector.getService().getContainer().getPipeline().getFirst().invoke(request, response);
+
+                if (request.isComet()) {
+                    if (!response.isClosed() && !response.isError()) {
+                        if (request.getAvailable() || (request.getContentLength() > 0 && (!request.isParametersParsed()))) {
+                            // Invoke a read event right away if there are available bytes
+                            if (event(req, res, SocketStatus.OPEN_READ)) {
+                                comet = true;
+                                res.action(ActionCode.COMET_BEGIN, null);
+                            }
+                        } else {
+                            comet = true;
+                            res.action(ActionCode.COMET_BEGIN, null);
+                        }
+                    } else {
+                        // Clear the filter chain, as otherwise it will not be reset elsewhere
+                        // since this is a Comet request
+                        request.setFilterChain(null);
+                    }
+                }
+
             }
             AsyncContextImpl asyncConImpl = (AsyncContextImpl)request.getAsyncContext();
             if (asyncConImpl != null) {
@@ -380,12 +552,15 @@
                         request.getContext().unbind(false, oldCL);
                     }
                 }
-            } else {
+            } else if (!comet) {
                 request.finishRequest();
                 response.finishResponse();
-                if (postParseSuccess) {
+                if (postParseSuccess &&
+                        request.getMappingData().context != null) {
                     // Log only if processing was invoked.
                     // If postParseRequest() failed, it has already logged it.
+                    // If context is null this was the start of a comet request
+                    // that failed and has already been logged.
                     request.getMappingData().context.logAccess(
                             request, response,
                             System.currentTimeMillis() - req.getStartTime(),
@@ -400,7 +575,7 @@
             AtomicBoolean error = new AtomicBoolean(false);
             res.action(ActionCode.IS_ERROR, error);
             // Recycle the wrapper request and response
-            if (!async || error.get()) {
+            if (!comet && !async || error.get()) {
                 request.recycle();
                 response.recycle();
             } else {
diff --git a/java/org/apache/catalina/connector/CoyoteInputStream.java b/java/org/apache/catalina/connector/CoyoteInputStream.java
index 723f1ca..d7af1d0 100644
--- a/java/org/apache/catalina/connector/CoyoteInputStream.java
+++ b/java/org/apache/catalina/connector/CoyoteInputStream.java
@@ -34,7 +34,8 @@
  */
 public class CoyoteInputStream extends ServletInputStream {
 
-    protected static final StringManager sm = StringManager.getManager(CoyoteInputStream.class);
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
 
 
     protected InputBuffer ib;
diff --git a/java/org/apache/catalina/connector/CoyoteOutputStream.java b/java/org/apache/catalina/connector/CoyoteOutputStream.java
index b6e131a..4d5949c 100644
--- a/java/org/apache/catalina/connector/CoyoteOutputStream.java
+++ b/java/org/apache/catalina/connector/CoyoteOutputStream.java
@@ -31,7 +31,8 @@
  */
 public class CoyoteOutputStream extends ServletOutputStream {
 
-    protected static final StringManager sm = StringManager.getManager(CoyoteOutputStream.class);
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
 
 
     // ----------------------------------------------------- Instance Variables
diff --git a/java/org/apache/catalina/connector/InputBuffer.java b/java/org/apache/catalina/connector/InputBuffer.java
index 0052c86..03fb73a 100644
--- a/java/org/apache/catalina/connector/InputBuffer.java
+++ b/java/org/apache/catalina/connector/InputBuffer.java
@@ -49,7 +49,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(InputBuffer.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     // -------------------------------------------------------------- Constants
@@ -213,7 +214,7 @@
 
 
     /**
-     * Clear cached encoders (to save memory for async requests).
+     * Clear cached encoders (to save memory for Comet requests).
      */
     public void clearEncoders() {
         encoders.clear();
@@ -283,6 +284,8 @@
         if (coyoteRequest.getReadListener() == null) {
             throw new IllegalStateException("not in non blocking mode.");
         }
+        // Need to check is finished before we check available() as BIO always
+        // returns 1 for isAvailable()
         if (isFinished()) {
             // If this is a non-container thread, need to trigger a read
             // which will eventually lead to a call to onAllDataRead() via a
diff --git a/java/org/apache/catalina/connector/LocalStrings.properties b/java/org/apache/catalina/connector/LocalStrings.properties
index 2ae0aae..a60e7e0 100644
--- a/java/org/apache/catalina/connector/LocalStrings.properties
+++ b/java/org/apache/catalina/connector/LocalStrings.properties
@@ -12,6 +12,9 @@
 # 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.
+
+cometEvent.nullRequest=The event object has been recycled and is no longer associated with a request
+
 coyoteAdapter.accesslogFail=Exception while attempting to add an entry to the access log
 coyoteAdapter.asyncDispatch=Exception while processing an asynchronous request
 coyoteAdapter.checkRecycled.request=Encountered a non-recycled request and recycled it forcedly.
diff --git a/java/org/apache/catalina/connector/LocalStrings_es.properties b/java/org/apache/catalina/connector/LocalStrings_es.properties
index 0b49c9e..229984c 100644
--- a/java/org/apache/catalina/connector/LocalStrings_es.properties
+++ b/java/org/apache/catalina/connector/LocalStrings_es.properties
@@ -51,6 +51,7 @@
 coyoteRequest.sessionEndAccessFail = Excepci\u00F3n disparada acabando acceso a sesi\u00F3n mientras se reciclaba el requerimiento
 requestFacade.nullRequest = El objeto de requerimiento ha sido reciclado y ya no est\u00E1 asociado con esta fachada
 responseFacade.nullResponse = El objeto de respuesta ha sido reciclado y ya no est\u00E1 asociado con esta fachada
+cometEvent.nullRequest = El objeto de evento ha sido reciclado y ya no est\u00E1 asociado con este requerimiento
 mapperListener.unknownDefaultHost = M\u00E1quina por defecto desconocida\: {0} para el conector [{1}]
 mapperListener.registerHost = Registrar m\u00E1quina {0} en dominio {1} para el conector [{2}]
 mapperListener.unregisterHost = Desregistrar m\u00E1quina {0} en dominio {1} para el conector [{2}]
diff --git a/java/org/apache/catalina/connector/OutputBuffer.java b/java/org/apache/catalina/connector/OutputBuffer.java
index 69fd7be..4a32650 100644
--- a/java/org/apache/catalina/connector/OutputBuffer.java
+++ b/java/org/apache/catalina/connector/OutputBuffer.java
@@ -45,7 +45,8 @@
 public class OutputBuffer extends Writer
     implements ByteChunk.ByteOutputChannel, CharChunk.CharOutputChannel {
 
-    private static final StringManager sm = StringManager.getManager(OutputBuffer.class);
+    private static final StringManager sm =
+            StringManager.getManager(Constants.Package);
 
     // -------------------------------------------------------------- Constants
 
@@ -248,7 +249,7 @@
 
 
     /**
-     * Clear cached encoders (to save memory for async requests).
+     * Clear cached encoders (to save memory for Comet requests).
      */
     public void clearEncoders() {
         encoders.clear();
diff --git a/java/org/apache/catalina/connector/Request.java b/java/org/apache/catalina/connector/Request.java
index 7cf3ada..1570f03 100644
--- a/java/org/apache/catalina/connector/Request.java
+++ b/java/org/apache/catalina/connector/Request.java
@@ -161,7 +161,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(Request.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     /**
@@ -223,6 +224,18 @@
 
 
     /**
+     * Associated event.
+     */
+    protected CometEventImpl event = null;
+
+
+    /**
+     * Comet state
+     */
+    protected boolean comet = false;
+
+
+    /**
      * The current dispatcher type.
      */
     protected DispatcherType internalDispatcherType = null;
@@ -434,6 +447,12 @@
         internalDispatcherType = null;
         requestDispatcherPath = null;
 
+        comet = false;
+        if (event != null) {
+            event.clear();
+            event = null;
+        }
+
         authType = null;
         inputBuffer.recycle();
         usingInputStream = false;
@@ -516,15 +535,25 @@
     }
 
     /**
-     * Clear cached encoders (to save memory for async requests).
+     * Clear cached encoders (to save memory for Comet requests).
      */
     public void clearEncoders() {
         inputBuffer.clearEncoders();
     }
 
 
+    /**
+     * Clear cached encoders (to save memory for Comet requests).
+     */
+    public boolean read()
+        throws IOException {
+        return (inputBuffer.realReadBytes(null, 0, 0) > 0);
+    }
+
+
     // -------------------------------------------------------- Request Methods
 
+
     /**
      * Associated Catalina connector.
      */
@@ -921,6 +950,8 @@
      * have names starting with "org.apache.tomcat" and include:
      * <ul>
      * <li>{@link Globals#SENDFILE_SUPPORTED_ATTR}</li>
+     * <li>{@link Globals#COMET_SUPPORTED_ATTR}</li>
+     * <li>{@link Globals#COMET_TIMEOUT_SUPPORTED_ATTR}</li>
      * </ul>
      * Connector implementations may return some, all or none of these
      * attributes and may also support additional attributes.
@@ -2481,6 +2512,33 @@
 
 
     /**
+     * Get the event associated with the request.
+     * @return the event
+     */
+    public CometEventImpl getEvent() {
+        if (event == null) {
+            event = new CometEventImpl(this, response);
+        }
+        return event;
+    }
+
+
+    /**
+     * Return true if the current request is handling Comet traffic.
+     */
+    public boolean isComet() {
+        return comet;
+    }
+
+
+    /**
+     * Set comet state.
+     */
+    public void setComet(boolean comet) {
+        this.comet = comet;
+    }
+
+    /**
      * return true if we have parsed parameters
      */
     public boolean isParametersParsed() {
@@ -2514,6 +2572,15 @@
         }
     }
 
+    public void cometClose() {
+        coyoteRequest.action(ActionCode.COMET_CLOSE,getEvent());
+        setComet(false);
+    }
+
+    public void setCometTimeout(long timeout) {
+        coyoteRequest.action(ActionCode.COMET_SETTIMEOUT, Long.valueOf(timeout));
+    }
+
     /**
      * @throws IOException If an I/O error occurs
      * @throws IllegalStateException If the response has been committed
@@ -3177,6 +3244,17 @@
     }
 
 
+    protected static final boolean isAlpha(String value) {
+        for (int i = 0; i < value.length(); i++) {
+            char c = value.charAt(i);
+            if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
     // ----------------------------------------------------- Special attributes handling
 
     private static interface SpecialAttributeAdapter {
@@ -3265,6 +3343,32 @@
                         // NO-OP
                     }
                 });
+        specialAttributes.put(Globals.COMET_SUPPORTED_ATTR,
+                new SpecialAttributeAdapter() {
+                    @Override
+                    public Object get(Request request, String name) {
+                        return Boolean.valueOf(
+                                request.getConnector().getProtocolHandler(
+                                        ).isCometSupported());
+                    }
+                    @Override
+                    public void set(Request request, String name, Object value) {
+                        // NO-OP
+                    }
+                });
+        specialAttributes.put(Globals.COMET_TIMEOUT_SUPPORTED_ATTR,
+                new SpecialAttributeAdapter() {
+                    @Override
+                    public Object get(Request request, String name) {
+                        return Boolean.valueOf(
+                                request.getConnector().getProtocolHandler(
+                                        ).isCometTimeoutSupported());
+                    }
+                    @Override
+                    public void set(Request request, String name, Object value) {
+                        // NO-OP
+                    }
+                });
         specialAttributes.put(Globals.SENDFILE_SUPPORTED_ATTR,
                 new SpecialAttributeAdapter() {
                     @Override
diff --git a/java/org/apache/catalina/connector/RequestFacade.java b/java/org/apache/catalina/connector/RequestFacade.java
index 98d2eb2..89b6516 100644
--- a/java/org/apache/catalina/connector/RequestFacade.java
+++ b/java/org/apache/catalina/connector/RequestFacade.java
@@ -242,7 +242,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(RequestFacade.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     // --------------------------------------------------------- Public Methods
diff --git a/java/org/apache/catalina/connector/Response.java b/java/org/apache/catalina/connector/Response.java
index c53ae5f..56cade5 100644
--- a/java/org/apache/catalina/connector/Response.java
+++ b/java/org/apache/catalina/connector/Response.java
@@ -98,7 +98,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(Response.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     // ----------------------------------------------------- Instance Variables
@@ -288,7 +289,7 @@
 
 
     /**
-     * Clear cached encoders (to save memory for async requests).
+     * Clear cached encoders (to save memory for Comet requests).
      */
     public void clearEncoders() {
         outputBuffer.clearEncoders();
diff --git a/java/org/apache/catalina/connector/ResponseFacade.java b/java/org/apache/catalina/connector/ResponseFacade.java
index 103a913..60c1d29 100644
--- a/java/org/apache/catalina/connector/ResponseFacade.java
+++ b/java/org/apache/catalina/connector/ResponseFacade.java
@@ -106,7 +106,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(ResponseFacade.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     /**
diff --git a/java/org/apache/catalina/core/ApplicationFilterChain.java b/java/org/apache/catalina/core/ApplicationFilterChain.java
index 7fffb24..fefcf75 100644
--- a/java/org/apache/catalina/core/ApplicationFilterChain.java
+++ b/java/org/apache/catalina/core/ApplicationFilterChain.java
@@ -14,8 +14,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+
+
 package org.apache.catalina.core;
 
+
 import java.io.IOException;
 import java.security.Principal;
 import java.security.PrivilegedActionException;
@@ -31,6 +34,10 @@
 
 import org.apache.catalina.Globals;
 import org.apache.catalina.InstanceEvent;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilter;
+import org.apache.catalina.comet.CometFilterChain;
+import org.apache.catalina.comet.CometProcessor;
 import org.apache.catalina.security.SecurityUtil;
 import org.apache.catalina.util.InstanceSupport;
 import org.apache.tomcat.util.ExceptionUtils;
@@ -45,7 +52,7 @@
  *
  * @author Craig R. McClanahan
  */
-final class ApplicationFilterChain implements FilterChain {
+final class ApplicationFilterChain implements FilterChain, CometFilterChain {
 
     // Used to enforce requirements of SRV.8.2 / SRV.14.2.5.1
     private static final ThreadLocal<ServletRequest> lastServicedRequest;
@@ -137,9 +144,24 @@
     private static final Class<?>[] classTypeUsedInService = new Class[]{
         ServletRequest.class, ServletResponse.class};
 
+    /**
+     * Static class array used when the SecurityManager is turned on and
+     * <code>doFilterEvent</code> is invoked.
+     */
+    private static final Class<?>[] cometClassType =
+        new Class[]{ CometEvent.class, CometFilterChain.class};
+
+    /**
+     * Static class array used when the SecurityManager is turned on and
+     * <code>event</code> is invoked.
+     */
+    private static final Class<?>[] classTypeUsedInEvent =
+        new Class[] { CometEvent.class };
+
 
     // ---------------------------------------------------- FilterChain Methods
 
+
     /**
      * Invoke the next filter in this chain, passing the specified request
      * and response.  If there are no more filters in this chain, invoke
@@ -302,6 +324,48 @@
 
 
     /**
+     * Process the event, using the security manager if the option is enabled.
+     *
+     * @param event the event to process
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet exception occurs
+     */
+    @Override
+    public void doFilterEvent(CometEvent event)
+        throws IOException, ServletException {
+
+        if( Globals.IS_SECURITY_ENABLED ) {
+            final CometEvent ev = event;
+            try {
+                java.security.AccessController.doPrivileged(
+                    new java.security.PrivilegedExceptionAction<Void>() {
+                        @Override
+                        public Void run()
+                            throws ServletException, IOException {
+                            internalDoFilterEvent(ev);
+                            return null;
+                        }
+                    }
+                );
+            } catch( PrivilegedActionException pe) {
+                Exception e = pe.getException();
+                if (e instanceof ServletException)
+                    throw (ServletException) e;
+                else if (e instanceof IOException)
+                    throw (IOException) e;
+                else if (e instanceof RuntimeException)
+                    throw (RuntimeException) e;
+                else
+                    throw new ServletException(e.getMessage(), e);
+            }
+        } else {
+            internalDoFilterEvent(event);
+        }
+    }
+
+
+    /**
      * The last request passed to a servlet for servicing from the current
      * thread.
      *
@@ -323,8 +387,125 @@
     }
 
 
+    private void internalDoFilterEvent(CometEvent event)
+        throws IOException, ServletException {
+
+        // Call the next filter if there is one
+        if (pos < n) {
+            ApplicationFilterConfig filterConfig = filters[pos++];
+            CometFilter filter = null;
+            try {
+                filter = (CometFilter) filterConfig.getFilter();
+                // FIXME: No instance listener processing for events for now
+                /*
+                support.fireInstanceEvent(InstanceEvent.BEFORE_FILTER_EVENT,
+                        filter, event);
+                        */
+
+                if( Globals.IS_SECURITY_ENABLED ) {
+                    final CometEvent ev = event;
+                    Principal principal =
+                        ev.getHttpServletRequest().getUserPrincipal();
+
+                    Object[] args = new Object[]{ev, this};
+                    SecurityUtil.doAsPrivilege("doFilterEvent", filter,
+                            cometClassType, args, principal);
+
+                } else {
+                    filter.doFilterEvent(event, this);
+                }
+
+                /*support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                        filter, event);*/
+            } catch (IOException e) {
+                /*
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);
+                            */
+                throw e;
+            } catch (ServletException e) {
+                /*
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);
+                            */
+                throw e;
+            } catch (RuntimeException e) {
+                /*
+                if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);
+                            */
+                throw e;
+            } catch (Throwable e) {
+                e = ExceptionUtils.unwrapInvocationTargetException(e);
+                ExceptionUtils.handleThrowable(e);
+                /*if (filter != null)
+                    support.fireInstanceEvent(InstanceEvent.AFTER_FILTER_EVENT,
+                            filter, event, e);*/
+                throw new ServletException
+                    (sm.getString("filterChain.filter"), e);
+            }
+            return;
+        }
+
+        // We fell off the end of the chain -- call the servlet instance
+        try {
+            /*
+            support.fireInstanceEvent(InstanceEvent.BEFORE_SERVICE_EVENT,
+                    servlet, request, response);
+                    */
+            if( Globals.IS_SECURITY_ENABLED ) {
+                final CometEvent ev = event;
+                Principal principal =
+                    ev.getHttpServletRequest().getUserPrincipal();
+                Object[] args = new Object[]{ ev };
+                SecurityUtil.doAsPrivilege("event",
+                        servlet,
+                        classTypeUsedInEvent,
+                        args,
+                        principal);
+            } else {
+                ((CometProcessor) servlet).event(event);
+            }
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response);*/
+        } catch (IOException e) {
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw e;
+        } catch (ServletException e) {
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw e;
+        } catch (RuntimeException e) {
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw e;
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            /*
+            support.fireInstanceEvent(InstanceEvent.AFTER_SERVICE_EVENT,
+                    servlet, request, response, e);
+                    */
+            throw new ServletException
+                (sm.getString("filterChain.servlet"), e);
+        }
+
+    }
+
+
     // -------------------------------------------------------- Package Methods
 
+
     /**
      * Add a filter to the set of filters that will be executed in this chain.
      *
diff --git a/java/org/apache/catalina/core/ApplicationFilterConfig.java b/java/org/apache/catalina/core/ApplicationFilterConfig.java
index db48281..1acc63d 100644
--- a/java/org/apache/catalina/core/ApplicationFilterConfig.java
+++ b/java/org/apache/catalina/core/ApplicationFilterConfig.java
@@ -37,7 +37,6 @@
 import org.apache.catalina.Context;
 import org.apache.catalina.Globals;
 import org.apache.catalina.security.SecurityUtil;
-import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.InstanceManager;
 import org.apache.tomcat.util.ExceptionUtils;
@@ -62,7 +61,8 @@
     protected static final StringManager sm =
         StringManager.getManager(Constants.Package);
 
-    private static final Log log = LogFactory.getLog(ApplicationFilterConfig.class);
+    private static final org.apache.juli.logging.Log log =
+        LogFactory.getLog(ApplicationFilterConfig.class);
 
     /**
      * Empty String collection to serve as the basis for empty enumerations.
diff --git a/java/org/apache/catalina/core/ApplicationFilterFactory.java b/java/org/apache/catalina/core/ApplicationFilterFactory.java
index d8c9978..2142530 100644
--- a/java/org/apache/catalina/core/ApplicationFilterFactory.java
+++ b/java/org/apache/catalina/core/ApplicationFilterFactory.java
@@ -22,7 +22,9 @@
 
 import org.apache.catalina.Globals;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometFilter;
 import org.apache.catalina.connector.Request;
+import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.descriptor.web.FilterMap;
 
 /**
@@ -85,13 +87,19 @@
         if (servlet == null)
             return (null);
 
+        boolean comet = false;
+
         // Create and initialize a filter chain object
         ApplicationFilterChain filterChain = null;
         if (request instanceof Request) {
             Request req = (Request) request;
+            comet = req.isComet();
             if (Globals.IS_SECURITY_ENABLED) {
                 // Security: Do not recycle
                 filterChain = new ApplicationFilterChain();
+                if (comet) {
+                    req.setFilterChain(filterChain);
+                }
             } else {
                 filterChain = (ApplicationFilterChain) req.getFilterChain();
                 if (filterChain == null) {
@@ -133,7 +141,23 @@
                 // FIXME - log configuration problem
                 continue;
             }
-            filterChain.addFilter(filterConfig);
+            boolean isCometFilter = false;
+            if (comet) {
+                try {
+                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
+                } catch (Exception e) {
+                    // Note: The try catch is there because getFilter has a lot of
+                    // declared exceptions. However, the filter is allocated much
+                    // earlier
+                    Throwable t = ExceptionUtils.unwrapInvocationTargetException(e);
+                    ExceptionUtils.handleThrowable(t);
+                }
+                if (isCometFilter) {
+                    filterChain.addFilter(filterConfig);
+                }
+            } else {
+                filterChain.addFilter(filterConfig);
+            }
         }
 
         // Add filters that match on servlet name second
@@ -149,11 +173,26 @@
                 // FIXME - log configuration problem
                 continue;
             }
-            filterChain.addFilter(filterConfig);
+            boolean isCometFilter = false;
+            if (comet) {
+                try {
+                    isCometFilter = filterConfig.getFilter() instanceof CometFilter;
+                } catch (Exception e) {
+                    // Note: The try catch is there because getFilter has a lot of
+                    // declared exceptions. However, the filter is allocated much
+                    // earlier
+                }
+                if (isCometFilter) {
+                    filterChain.addFilter(filterConfig);
+                }
+            } else {
+                filterChain.addFilter(filterConfig);
+            }
         }
 
         // Return the completed filter chain
-        return filterChain;
+        return (filterChain);
+
     }
 
 
diff --git a/java/org/apache/catalina/core/ContainerBase.java b/java/org/apache/catalina/core/ContainerBase.java
index 86c50c8..29948e3 100644
--- a/java/org/apache/catalina/core/ContainerBase.java
+++ b/java/org/apache/catalina/core/ContainerBase.java
@@ -131,7 +131,8 @@
 public abstract class ContainerBase extends LifecycleMBeanBase
         implements Container {
 
-    private static final Log log = LogFactory.getLog(ContainerBase.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( ContainerBase.class );
 
     /**
      * Perform addChild with the permissions of this class.
@@ -751,7 +752,9 @@
      */
     @Override
     public void addPropertyChangeListener(PropertyChangeListener listener) {
+
         support.addPropertyChangeListener(listener);
+
     }
 
 
@@ -763,12 +766,13 @@
      */
     @Override
     public Container findChild(String name) {
-        if (name == null) {
-            return null;
-        }
+
+        if (name == null)
+            return (null);
         synchronized (children) {
             return children.get(name);
         }
+
     }
 
 
@@ -778,10 +782,12 @@
      */
     @Override
     public Container[] findChildren() {
+
         synchronized (children) {
             Container results[] = new Container[children.size()];
             return children.values().toArray(results);
         }
+
     }
 
 
@@ -892,13 +898,11 @@
         logger = null;
         getLogger();
         Cluster cluster = getClusterInternal();
-        if (cluster instanceof Lifecycle) {
+        if ((cluster != null) && (cluster instanceof Lifecycle))
             ((Lifecycle) cluster).start();
-        }
         Realm realm = getRealmInternal();
-        if (realm instanceof Lifecycle) {
+        if ((realm != null) && (realm instanceof Lifecycle))
             ((Lifecycle) realm).start();
-        }
 
         // Start our child containers, if any
         Container children[] = findChildren();
@@ -979,11 +983,11 @@
 
         // Stop our subordinate components, if any
         Realm realm = getRealmInternal();
-        if (realm instanceof Lifecycle) {
+        if ((realm != null) && (realm instanceof Lifecycle)) {
             ((Lifecycle) realm).stop();
         }
         Cluster cluster = getClusterInternal();
-        if (cluster instanceof Lifecycle) {
+        if ((cluster != null) && (cluster instanceof Lifecycle)) {
             ((Lifecycle) cluster).stop();
         }
     }
@@ -992,11 +996,11 @@
     protected void destroyInternal() throws LifecycleException {
 
         Realm realm = getRealmInternal();
-        if (realm instanceof Lifecycle) {
+        if ((realm != null) && (realm instanceof Lifecycle)) {
             ((Lifecycle) realm).destroy();
         }
         Cluster cluster = getClusterInternal();
-        if (cluster instanceof Lifecycle) {
+        if ((cluster != null) && (cluster instanceof Lifecycle)) {
             ((Lifecycle) cluster).destroy();
         }
 
diff --git a/java/org/apache/catalina/core/StandardContext.java b/java/org/apache/catalina/core/StandardContext.java
index 5404987..8a0266a 100644
--- a/java/org/apache/catalina/core/StandardContext.java
+++ b/java/org/apache/catalina/core/StandardContext.java
@@ -977,15 +977,18 @@
 
     @Override
     public Authenticator getAuthenticator() {
+        if (this instanceof Authenticator)
+            return (Authenticator) this;
+
         Pipeline pipeline = getPipeline();
         if (pipeline != null) {
             Valve basic = pipeline.getBasic();
-            if (basic instanceof Authenticator)
+            if ((basic != null) && (basic instanceof Authenticator))
                 return (Authenticator) basic;
-            for (Valve valve : pipeline.getValves()) {
-                if (valve instanceof Authenticator) {
-                    return (Authenticator) valve;
-                }
+            Valve valves[] = pipeline.getValves();
+            for (int i = 0; i < valves.length; i++) {
+                if (valves[i] instanceof Authenticator)
+                    return (Authenticator) valves[i];
             }
         }
         return null;
@@ -4996,9 +4999,8 @@
             if (ok) {
                 // Start our subordinate components, if any
                 Loader loader = getLoader();
-                if (loader instanceof Lifecycle) {
+                if ((loader != null) && (loader instanceof Lifecycle))
                     ((Lifecycle) loader).start();
-                }
 
                 // since the loader just started, the webapp classloader is now
                 // created.
@@ -5022,13 +5024,11 @@
                 getLogger();
 
                 Cluster cluster = getClusterInternal();
-                if (cluster instanceof Lifecycle) {
+                if ((cluster != null) && (cluster instanceof Lifecycle))
                     ((Lifecycle) cluster).start();
-                }
                 Realm realm = getRealmInternal();
-                if (realm instanceof Lifecycle) {
+                if ((realm != null) && (realm instanceof Lifecycle))
                     ((Lifecycle) realm).start();
-                }
 
                 // Notify our interested LifecycleListeners
                 fireLifecycleEvent(Lifecycle.CONFIGURE_START_EVENT, null);
@@ -5148,7 +5148,7 @@
             try {
                 // Start manager
                 Manager manager = getManager();
-                if (manager instanceof Lifecycle) {
+                if ((manager != null) && (manager instanceof Lifecycle)) {
                     ((Lifecycle) manager).start();
                 }
             } catch(Exception e) {
@@ -5343,7 +5343,8 @@
             filterStop();
 
             Manager manager = getManager();
-            if (manager instanceof Lifecycle && ((Lifecycle) manager).getState().isAvailable()) {
+            if (manager != null && manager instanceof Lifecycle &&
+                    ((Lifecycle) manager).getState().isAvailable()) {
                 ((Lifecycle) manager).stop();
             }
 
@@ -5378,15 +5379,15 @@
                 context.clearAttributes();
 
             Realm realm = getRealmInternal();
-            if (realm instanceof Lifecycle) {
+            if ((realm != null) && (realm instanceof Lifecycle)) {
                 ((Lifecycle) realm).stop();
             }
             Cluster cluster = getClusterInternal();
-            if (cluster instanceof Lifecycle) {
+            if ((cluster != null) && (cluster instanceof Lifecycle)) {
                 ((Lifecycle) cluster).stop();
             }
             Loader loader = getLoader();
-            if (loader instanceof Lifecycle) {
+            if ((loader != null) && (loader instanceof Lifecycle)) {
                 ((Lifecycle) loader).stop();
             }
 
@@ -5460,12 +5461,12 @@
         }
 
         Loader loader = getLoader();
-        if (loader instanceof Lifecycle) {
+        if ((loader != null) && (loader instanceof Lifecycle)) {
             ((Lifecycle) loader).destroy();
         }
 
         Manager manager = getManager();
-        if (manager instanceof Lifecycle) {
+        if ((manager != null) && (manager instanceof Lifecycle)) {
             ((Lifecycle) manager).destroy();
         }
 
diff --git a/java/org/apache/catalina/core/StandardContextValve.java b/java/org/apache/catalina/core/StandardContextValve.java
index f72d898..8419fce 100644
--- a/java/org/apache/catalina/core/StandardContextValve.java
+++ b/java/org/apache/catalina/core/StandardContextValve.java
@@ -24,6 +24,7 @@
 
 import org.apache.catalina.Container;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.valves.ValveBase;
@@ -104,4 +105,27 @@
         }
         wrapper.getPipeline().getFirst().invoke(request, response);
     }
+
+
+    /**
+     * Select the appropriate child Wrapper to process this request,
+     * based on the specified request URI.  If no matching Wrapper can
+     * be found, return an appropriate HTTP error.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     * @param event
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Select the Wrapper to be used for this Request
+        Wrapper wrapper = request.getWrapper();
+
+        wrapper.getPipeline().getFirst().event(request, response, event);
+    }
 }
diff --git a/java/org/apache/catalina/core/StandardEngineValve.java b/java/org/apache/catalina/core/StandardEngineValve.java
index 8ec5ef3..41c5a00 100644
--- a/java/org/apache/catalina/core/StandardEngineValve.java
+++ b/java/org/apache/catalina/core/StandardEngineValve.java
@@ -22,6 +22,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.catalina.Host;
+import org.apache.catalina.comet.CometEvent;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.valves.ValveBase;
@@ -87,4 +88,25 @@
         host.getPipeline().getFirst().invoke(request, response);
 
     }
+
+
+    /**
+     * Process Comet event.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     * @param event the event
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Ask this Host to process this request
+        request.getHost().getPipeline().getFirst().event(request, response, event);
+
+    }
+
 }
diff --git a/java/org/apache/catalina/core/StandardHost.java b/java/org/apache/catalina/core/StandardHost.java
index c696785..6c7e23c 100644
--- a/java/org/apache/catalina/core/StandardHost.java
+++ b/java/org/apache/catalina/core/StandardHost.java
@@ -40,8 +40,6 @@
 import org.apache.catalina.LifecycleListener;
 import org.apache.catalina.Valve;
 import org.apache.catalina.loader.WebappClassLoaderBase;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
 
 /**
@@ -54,7 +52,8 @@
  */
 public class StandardHost extends ContainerBase implements Host {
 
-    private static final Log log = LogFactory.getLog(StandardHost.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( StandardHost.class );
 
     // ----------------------------------------------------------- Constructors
 
diff --git a/java/org/apache/catalina/core/StandardHostValve.java b/java/org/apache/catalina/core/StandardHostValve.java
index 69e8150..a5643be 100644
--- a/java/org/apache/catalina/core/StandardHostValve.java
+++ b/java/org/apache/catalina/core/StandardHostValve.java
@@ -27,6 +27,7 @@
 import org.apache.catalina.Context;
 import org.apache.catalina.Globals;
 import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent;
 import org.apache.catalina.connector.ClientAbortException;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
@@ -195,6 +196,52 @@
     }
 
 
+    /**
+     * Process Comet event.
+     *
+     * @param request Request to be processed
+     * @param response Response to be produced
+     * @param event the event
+     *
+     * @exception IOException if an input/output error occurred
+     * @exception ServletException if a servlet error occurred
+     */
+    @Override
+    public final void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Select the Context to be used for this Request
+        Context context = request.getContext();
+
+        context.bind(false, MY_CLASSLOADER);
+
+        // Ask this Context to process this request
+        context.getPipeline().getFirst().event(request, response, event);
+
+
+        // Error page processing
+        response.setSuspended(false);
+
+        Throwable t = (Throwable) request.getAttribute(
+                RequestDispatcher.ERROR_EXCEPTION);
+
+        if (t != null) {
+            throwable(request, response, t);
+        } else {
+            status(request, response);
+        }
+
+        // Access a session (if present) to update last accessed time, based on a
+        // strict interpretation of the specification
+        if (ACCESS_SESSION) {
+            request.getSession(false);
+        }
+
+        // Restore the context classloader
+        context.unbind(false, MY_CLASSLOADER);
+    }
+
+
     // -------------------------------------------------------- Private Methods
 
     /**
diff --git a/java/org/apache/catalina/core/StandardService.java b/java/org/apache/catalina/core/StandardService.java
index 734e976..1926ebb 100644
--- a/java/org/apache/catalina/core/StandardService.java
+++ b/java/org/apache/catalina/core/StandardService.java
@@ -139,13 +139,11 @@
     public void setContainer(Container container) {
 
         Container oldContainer = this.container;
-        if (oldContainer instanceof Engine) {
+        if ((oldContainer != null) && (oldContainer instanceof Engine))
             ((Engine) oldContainer).setService(null);
-        }
         this.container = container;
-        if (this.container instanceof Engine) {
+        if ((this.container != null) && (this.container instanceof Engine))
             ((Engine) this.container).setService(this);
-        }
         if (getState().isAvailable() && (this.container != null)) {
             try {
                 this.container.start();
diff --git a/java/org/apache/catalina/core/StandardWrapper.java b/java/org/apache/catalina/core/StandardWrapper.java
index 784cd79..e26febc 100644
--- a/java/org/apache/catalina/core/StandardWrapper.java
+++ b/java/org/apache/catalina/core/StandardWrapper.java
@@ -665,7 +665,7 @@
         if (!getState().isAvailable())
             return;
 
-        if (getServlet() instanceof PeriodicEventListener) {
+        if (getServlet() != null && (getServlet() instanceof PeriodicEventListener)) {
             ((PeriodicEventListener) getServlet()).periodicEvent();
         }
     }
diff --git a/java/org/apache/catalina/core/StandardWrapperFacade.java b/java/org/apache/catalina/core/StandardWrapperFacade.java
index fc0691a..4ef64c0 100644
--- a/java/org/apache/catalina/core/StandardWrapperFacade.java
+++ b/java/org/apache/catalina/core/StandardWrapperFacade.java
@@ -76,9 +76,8 @@
     public ServletContext getServletContext() {
         if (context == null) {
             context = config.getServletContext();
-            if (context instanceof ApplicationContext) {
+            if ((context != null) && (context instanceof ApplicationContext))
                 context = ((ApplicationContext) context).getFacade();
-            }
         }
         return (context);
     }
diff --git a/java/org/apache/catalina/core/StandardWrapperValve.java b/java/org/apache/catalina/core/StandardWrapperValve.java
index 9e8b92f..67bb01f 100644
--- a/java/org/apache/catalina/core/StandardWrapperValve.java
+++ b/java/org/apache/catalina/core/StandardWrapperValve.java
@@ -32,6 +32,8 @@
 import org.apache.catalina.Context;
 import org.apache.catalina.Globals;
 import org.apache.catalina.LifecycleException;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometProcessor;
 import org.apache.catalina.connector.ClientAbortException;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
@@ -161,6 +163,14 @@
             servlet = null;
         }
 
+        // Identify if the request is Comet related now that the servlet has been allocated
+        boolean comet = false;
+        if (servlet instanceof CometProcessor && request.getAttribute(
+                Globals.COMET_SUPPORTED_ATTR) == Boolean.TRUE) {
+            comet = true;
+            request.setComet(true);
+        }
+
         MessageBytes requestPathMB = request.getRequestPathMB();
         DispatcherType dispatcherType = DispatcherType.REQUEST;
         if (request.getDispatcherType()==DispatcherType.ASYNC) dispatcherType = DispatcherType.ASYNC;
@@ -173,6 +183,9 @@
         ApplicationFilterChain filterChain =
             factory.createFilterChain(request, wrapper, servlet);
 
+        // Reset comet flag value after creating the filter chain
+        request.setComet(false);
+
         // Call the filter chain for this request
         // NOTE: This also calls the servlet's service() method
         try {
@@ -183,6 +196,9 @@
                         SystemLogHandler.startCapture();
                         if (request.isAsyncDispatching()) {
                             ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
+                        } else if (comet) {
+                            filterChain.doFilterEvent(request.getEvent());
+                            request.setComet(true);
                         } else {
                             filterChain.doFilter(request.getRequest(),
                                     response.getResponse());
@@ -196,6 +212,9 @@
                 } else {
                     if (request.isAsyncDispatching()) {
                         ((AsyncContextImpl)request.getAsyncContext()).doInternalDispatch();
+                    } else if (comet) {
+                        request.setComet(true);
+                        filterChain.doFilterEvent(request.getEvent());
                     } else {
                         filterChain.doFilter
                             (request.getRequest(), response.getResponse());
@@ -253,7 +272,13 @@
 
         // Release the filter chain (if any) for this request
         if (filterChain != null) {
-            filterChain.release();
+            if (request.isComet()) {
+                // If this is a Comet request, then the same chain will be used for the
+                // processing of all subsequent events.
+                filterChain.reuse();
+            } else {
+                filterChain.release();
+            }
         }
 
         // Deallocate the allocated servlet instance
@@ -297,8 +322,177 @@
     }
 
 
+    /**
+     * Process a Comet event. The main differences here are to not use sendError
+     * (the response is committed), to avoid creating a new filter chain
+     * (which would work but be pointless), and a few very minor tweaks.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    @Override
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Initialize local variables we may need
+        Throwable throwable = null;
+        // This should be a Request attribute...
+        long t1=System.currentTimeMillis();
+        // FIXME: Add a flag to count the total amount of events processed ? requestCount++;
+
+        StandardWrapper wrapper = (StandardWrapper) getContainer();
+        if (wrapper == null) {
+            // Context has been shutdown. Nothing to do here.
+            return;
+        }
+
+        Servlet servlet = null;
+        Context context = (Context) wrapper.getParent();
+
+        // Check for the application being marked unavailable
+        boolean unavailable = !context.getState().isAvailable() ||
+                wrapper.isUnavailable();
+
+        // Allocate a servlet instance to process this request
+        try {
+            if (!unavailable) {
+                servlet = wrapper.allocate();
+            }
+        } catch (UnavailableException e) {
+            // The response is already committed, so it's not possible to do anything
+        } catch (ServletException e) {
+            container.getLogger().error(sm.getString("standardWrapper.allocateException",
+                             wrapper.getName()), StandardWrapper.getRootCause(e));
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.allocateException",
+                             wrapper.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+            servlet = null;
+        }
+
+        MessageBytes requestPathMB = request.getRequestPathMB();
+        request.setAttribute(Globals.DISPATCHER_TYPE_ATTR,
+                DispatcherType.REQUEST);
+        request.setAttribute(Globals.DISPATCHER_REQUEST_PATH_ATTR,
+                requestPathMB);
+        // Get the current (unchanged) filter chain for this request
+        ApplicationFilterChain filterChain =
+            (ApplicationFilterChain) request.getFilterChain();
+
+        // Call the filter chain for this request
+        // NOTE: This also calls the servlet's event() method
+        try {
+            if ((servlet != null) && (filterChain != null)) {
+
+                // Swallow output if needed
+                if (context.getSwallowOutput()) {
+                    try {
+                        SystemLogHandler.startCapture();
+                        filterChain.doFilterEvent(request.getEvent());
+                    } finally {
+                        String log = SystemLogHandler.stopCapture();
+                        if (log != null && log.length() > 0) {
+                            context.getLogger().info(log);
+                        }
+                    }
+                } else {
+                    filterChain.doFilterEvent(request.getEvent());
+                }
+
+            }
+        } catch (ClientAbortException e) {
+            throwable = e;
+            exception(request, response, e);
+        } catch (IOException e) {
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        } catch (UnavailableException e) {
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            // Do not save exception in 'throwable', because we
+            // do not want to do exception(request, response, e) processing
+        } catch (ServletException e) {
+            Throwable rootCause = StandardWrapper.getRootCause(e);
+            if (!(rootCause instanceof ClientAbortException)) {
+                container.getLogger().error(sm.getString(
+                        "standardWrapper.serviceExceptionRoot",
+                        wrapper.getName(), context.getName(), e.getMessage()),
+                        rootCause);
+            }
+            throwable = e;
+            exception(request, response, e);
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString(
+                    "standardWrapper.serviceException", wrapper.getName(),
+                    context.getName()), e);
+            throwable = e;
+            exception(request, response, e);
+        }
+
+        // Release the filter chain (if any) for this request
+        if (filterChain != null) {
+            filterChain.reuse();
+        }
+
+        // Deallocate the allocated servlet instance
+        try {
+            if (servlet != null) {
+                wrapper.deallocate(servlet);
+            }
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.deallocateException",
+                             wrapper.getName()), e);
+            if (throwable == null) {
+                throwable = e;
+                exception(request, response, e);
+            }
+        }
+
+        // If this servlet has been marked permanently unavailable,
+        // unload it and release this instance
+        try {
+            if ((servlet != null) &&
+                (wrapper.getAvailable() == Long.MAX_VALUE)) {
+                wrapper.unload();
+            }
+        } catch (Throwable e) {
+            ExceptionUtils.handleThrowable(e);
+            container.getLogger().error(sm.getString("standardWrapper.unloadException",
+                             wrapper.getName()), e);
+            if (throwable == null) {
+                throwable = e;
+                exception(request, response, e);
+            }
+        }
+
+        long t2=System.currentTimeMillis();
+
+        long time=t2-t1;
+        processingTime += time;
+        if( time > maxTime) maxTime=time;
+        if( time < minTime) minTime=time;
+
+    }
+
+
     // -------------------------------------------------------- Private Methods
 
+
     /**
      * Handle the specified ServletException encountered while processing
      * the specified Request to produce the specified Response.  Any
diff --git a/java/org/apache/catalina/deploy/Constants.java b/java/org/apache/catalina/deploy/Constants.java
new file mode 100644
index 0000000..6b3cf21
--- /dev/null
+++ b/java/org/apache/catalina/deploy/Constants.java
@@ -0,0 +1,26 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.deploy;
+
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.deploy";
+
+}
diff --git a/java/org/apache/catalina/deploy/NamingResourcesImpl.java b/java/org/apache/catalina/deploy/NamingResourcesImpl.java
index 9fdcb1a..fb61665 100644
--- a/java/org/apache/catalina/deploy/NamingResourcesImpl.java
+++ b/java/org/apache/catalina/deploy/NamingResourcesImpl.java
@@ -70,7 +70,8 @@
 
     private static final Log log = LogFactory.getLog(NamingResourcesImpl.class);
 
-    private static final StringManager sm = StringManager.getManager(NamingResourcesImpl.class);
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
     private volatile boolean resourceRequireExplicitRegistration = false;
 
diff --git a/java/org/apache/catalina/filters/CorsFilter.java b/java/org/apache/catalina/filters/CorsFilter.java
index 4db5f41..7c76000 100644
--- a/java/org/apache/catalina/filters/CorsFilter.java
+++ b/java/org/apache/catalina/filters/CorsFilter.java
@@ -1033,6 +1033,15 @@
             new HashSet<>(Arrays.asList("OPTIONS", "GET", "HEAD", "POST", "PUT",
                     "DELETE", "TRACE", "CONNECT"));
     /**
+     * {@link Collection} of non-simple HTTP methods. Case sensitive.
+     * @deprecated Not used. Will be removed in Tomcat 9.0.x onwards. All HTTP
+     *             methods not in {@link #HTTP_METHODS} are assumed to be
+     *             non-simple.
+     */
+    @Deprecated
+    public static final Collection<String> COMPLEX_HTTP_METHODS =
+            new HashSet<>(Arrays.asList("PUT", "DELETE", "TRACE", "CONNECT"));
+    /**
      * {@link Collection} of Simple HTTP methods. Case sensitive.
      *
      * @see  <a href="http://www.w3.org/TR/cors/#terminology"
diff --git a/java/org/apache/catalina/filters/FailedRequestFilter.java b/java/org/apache/catalina/filters/FailedRequestFilter.java
index 52f49dd..045372d 100644
--- a/java/org/apache/catalina/filters/FailedRequestFilter.java
+++ b/java/org/apache/catalina/filters/FailedRequestFilter.java
@@ -25,6 +25,9 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.apache.catalina.Globals;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilter;
+import org.apache.catalina.comet.CometFilterChain;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 
@@ -40,7 +43,7 @@
  * <code>request.getInputStream()</code> and <code>request.getReader()</code>,
  * if requests parsed by them do not use standard value for content mime-type.
  */
-public class FailedRequestFilter extends FilterBase {
+public class FailedRequestFilter extends FilterBase implements CometFilter {
 
     private static final Log log = LogFactory.getLog(FailedRequestFilter.class);
 
@@ -60,6 +63,19 @@
         chain.doFilter(request, response);
     }
 
+    @Override
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+            throws IOException, ServletException {
+        if (event.getEventType() == CometEvent.EventType.BEGIN
+                && !isGoodRequest(event.getHttpServletRequest())) {
+            event.getHttpServletResponse().sendError(
+                    HttpServletResponse.SC_BAD_REQUEST);
+            event.close();
+            return;
+        }
+        chain.doFilterEvent(event);
+    }
+
     private boolean isGoodRequest(ServletRequest request) {
         // Trigger parsing of parameters
         request.getParameter("none");
diff --git a/java/org/apache/catalina/filters/RemoteAddrFilter.java b/java/org/apache/catalina/filters/RemoteAddrFilter.java
index 4da7d2c..8fe9fc6 100644
--- a/java/org/apache/catalina/filters/RemoteAddrFilter.java
+++ b/java/org/apache/catalina/filters/RemoteAddrFilter.java
@@ -16,6 +16,7 @@
  */
 package org.apache.catalina.filters;
 
+
 import java.io.IOException;
 
 import javax.servlet.FilterChain;
@@ -23,9 +24,12 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilterChain;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 
+
 /**
  * Concrete implementation of <code>RequestFilter</code> that filters
  * based on the string representation of the remote client's IP address.
@@ -68,6 +72,24 @@
 
     }
 
+    /**
+     * Extract the desired request property, and pass it (along with the comet
+     * event and filter chain) to the protected <code>process()</code> method
+     * to perform the actual filtering.
+     *
+     * @param event The comet event to be processed
+     * @param chain The filter chain for this event
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+            throws IOException, ServletException {
+        processCometEvent(event.getHttpServletRequest().getRemoteAddr(),
+                event, chain);
+    }
+
     @Override
     protected Log getLogger() {
         return log;
diff --git a/java/org/apache/catalina/filters/RemoteHostFilter.java b/java/org/apache/catalina/filters/RemoteHostFilter.java
index 0871a40..de8a327 100644
--- a/java/org/apache/catalina/filters/RemoteHostFilter.java
+++ b/java/org/apache/catalina/filters/RemoteHostFilter.java
@@ -16,6 +16,7 @@
  */
 package org.apache.catalina.filters;
 
+
 import java.io.IOException;
 
 import javax.servlet.FilterChain;
@@ -23,9 +24,12 @@
 import javax.servlet.ServletRequest;
 import javax.servlet.ServletResponse;
 
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilterChain;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
 
+
 /**
  * Concrete implementation of <code>RequestFilter</code> that filters
  * based on the remote client's host name.
@@ -63,6 +67,24 @@
 
     }
 
+    /**
+     * Extract the desired request property, and pass it (along with the comet
+     * event and filter chain) to the protected <code>process()</code> method
+     * to perform the actual filtering.
+     *
+     * @param event The comet event to be processed
+     * @param chain The filter chain for this event
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void doFilterEvent(CometEvent event, CometFilterChain chain)
+            throws IOException, ServletException {
+        processCometEvent(event.getHttpServletRequest().getRemoteHost(),
+                event, chain);
+    }
+
     @Override
     protected Log getLogger() {
         return log;
diff --git a/java/org/apache/catalina/filters/RequestFilter.java b/java/org/apache/catalina/filters/RequestFilter.java
index 859c36f..e6c79dd 100644
--- a/java/org/apache/catalina/filters/RequestFilter.java
+++ b/java/org/apache/catalina/filters/RequestFilter.java
@@ -26,6 +26,10 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletResponse;
 
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometFilter;
+import org.apache.catalina.comet.CometFilterChain;
+
 /**
  * Implementation of a Filter that performs filtering based on comparing the
  * appropriate request property (selected based on which subclass you choose
@@ -51,7 +55,7 @@
  * <li>The request will be rejected with a "Forbidden" HTTP response.</li>
  * </ul>
  */
-public abstract class RequestFilter extends FilterBase {
+public abstract class RequestFilter extends FilterBase implements CometFilter {
 
 
     // ----------------------------------------------------- Instance Variables
@@ -210,6 +214,28 @@
 
 
     /**
+     * Perform the filtering that has been configured for this Filter, matching
+     * against the specified request property.
+     *
+     * @param property  The property to check against the allow/deny rules
+     * @param event     The comet event to be filtered
+     * @param chain     The comet filter chain
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    protected void processCometEvent(String property, CometEvent event,
+            CometFilterChain chain) throws IOException, ServletException {
+        HttpServletResponse response = event.getHttpServletResponse();
+
+        if (isAllowed(property)) {
+            chain.doFilterEvent(event);
+        } else {
+            response.sendError(denyStatus);
+            event.close();
+        }
+    }
+
+    /**
      * Process the allow and deny rules for the provided property.
      *
      * @param property  The property to test against the allow and deny lists
diff --git a/java/org/apache/catalina/ha/ClusterListener.java b/java/org/apache/catalina/ha/ClusterListener.java
index 5ab903e..1c951f1 100644
--- a/java/org/apache/catalina/ha/ClusterListener.java
+++ b/java/org/apache/catalina/ha/ClusterListener.java
@@ -21,8 +21,6 @@
 
 import org.apache.catalina.tribes.ChannelListener;
 import org.apache.catalina.tribes.Member;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 
 /**
@@ -33,7 +31,8 @@
  */
 public abstract class ClusterListener implements ChannelListener {
 
-    private static final Log log = LogFactory.getLog(ClusterListener.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(ClusterListener.class);
 
     //--Instance Variables--------------------------------------
 
diff --git a/java/org/apache/catalina/ha/context/ReplicatedContext.java b/java/org/apache/catalina/ha/context/ReplicatedContext.java
index 39627d8..fb1b9b6 100644
--- a/java/org/apache/catalina/ha/context/ReplicatedContext.java
+++ b/java/org/apache/catalina/ha/context/ReplicatedContext.java
@@ -87,7 +87,7 @@
 
         Map<String,Object> map =
                 ((ReplApplContext)this.context).getAttributeMap();
-        if (map instanceof ReplicatedMap) {
+        if ( map!=null && map instanceof ReplicatedMap) {
             ((ReplicatedMap<?,?>)map).breakdown();
         }
     }
diff --git a/java/org/apache/catalina/ha/deploy/Constants.java b/java/org/apache/catalina/ha/deploy/Constants.java
new file mode 100644
index 0000000..9e85e27
--- /dev/null
+++ b/java/org/apache/catalina/ha/deploy/Constants.java
@@ -0,0 +1,27 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.ha.deploy;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.deploy</code>
+ * package.
+ */
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.ha.deploy";
+
+}
diff --git a/java/org/apache/catalina/ha/deploy/FarmWarDeployer.java b/java/org/apache/catalina/ha/deploy/FarmWarDeployer.java
index e6eefb0..2c165c7 100644
--- a/java/org/apache/catalina/ha/deploy/FarmWarDeployer.java
+++ b/java/org/apache/catalina/ha/deploy/FarmWarDeployer.java
@@ -61,7 +61,8 @@
         implements ClusterDeployer, FileChangeListener {
     /*--Static Variables----------------------------------------*/
     private static final Log log = LogFactory.getLog(FarmWarDeployer.class);
-    private static final StringManager sm = StringManager.getManager(FarmWarDeployer.class);
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
     /*--Instance Variables--------------------------------------*/
     protected boolean started = false;
diff --git a/java/org/apache/catalina/ha/deploy/FileMessageFactory.java b/java/org/apache/catalina/ha/deploy/FileMessageFactory.java
index 2823876..0e3ded0 100644
--- a/java/org/apache/catalina/ha/deploy/FileMessageFactory.java
+++ b/java/org/apache/catalina/ha/deploy/FileMessageFactory.java
@@ -47,7 +47,8 @@
 public class FileMessageFactory {
     /*--Static Variables----------------------------------------*/
     private static final Log log = LogFactory.getLog(FileMessageFactory.class);
-    private static final StringManager sm = StringManager.getManager(FileMessageFactory.class);
+    private static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
     /**
      * The number of bytes that we read from file
diff --git a/java/org/apache/catalina/ha/session/BackupManager.java b/java/org/apache/catalina/ha/session/BackupManager.java
index bf3ce09..5c543be 100644
--- a/java/org/apache/catalina/ha/session/BackupManager.java
+++ b/java/org/apache/catalina/ha/session/BackupManager.java
@@ -44,7 +44,7 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(BackupManager.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
 
     protected static final long DEFAULT_REPL_TIMEOUT = 15000;//15 seconds
 
@@ -100,7 +100,7 @@
 //=========================================================================
     @Override
     public void objectMadePrimary(Object key, Object value) {
-        if (value instanceof DeltaSession) {
+        if (value!=null && value instanceof DeltaSession) {
             DeltaSession session = (DeltaSession)value;
             synchronized (session) {
                 session.access();
diff --git a/java/org/apache/catalina/ha/session/ClusterSessionListener.java b/java/org/apache/catalina/ha/session/ClusterSessionListener.java
index c3cfccf..65b3eea 100644
--- a/java/org/apache/catalina/ha/session/ClusterSessionListener.java
+++ b/java/org/apache/catalina/ha/session/ClusterSessionListener.java
@@ -51,7 +51,7 @@
      */
     @Override
     public void messageReceived(ClusterMessage myobj) {
-        if (myobj instanceof SessionMessage) {
+        if (myobj != null && myobj instanceof SessionMessage) {
             SessionMessage msg = (SessionMessage) myobj;
             String ctxname = msg.getContextName();
             //check if the message is a EVT_GET_ALL_SESSIONS,
diff --git a/java/org/apache/catalina/ha/session/Constants.java b/java/org/apache/catalina/ha/session/Constants.java
new file mode 100644
index 0000000..27dcf47
--- /dev/null
+++ b/java/org/apache/catalina/ha/session/Constants.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.ha.session;
+
+/**
+ * Manifest constants for the <code>org.apache.catalina.ha.session</code>
+ * package.
+ *
+ * @author Peter Rossbach Pero
+ */
+
+public class Constants {
+
+    public static final String Package = "org.apache.catalina.ha.session";
+
+}
diff --git a/java/org/apache/catalina/ha/session/DeltaManager.java b/java/org/apache/catalina/ha/session/DeltaManager.java
index 850c34d..9aba9f7 100644
--- a/java/org/apache/catalina/ha/session/DeltaManager.java
+++ b/java/org/apache/catalina/ha/session/DeltaManager.java
@@ -36,8 +36,6 @@
 import org.apache.catalina.session.ManagerBase;
 import org.apache.catalina.tribes.Member;
 import org.apache.catalina.tribes.io.ReplicationStream;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -59,12 +57,13 @@
 public class DeltaManager extends ClusterManagerBase{
 
     // ---------------------------------------------------- Security Classes
-    public final Log log = LogFactory.getLog(DeltaManager.class);
+    public final org.apache.juli.logging.Log log =
+            org.apache.juli.logging.LogFactory.getLog(DeltaManager.class);
 
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(DeltaManager.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
 
     // ----------------------------------------------------- Instance Variables
 
@@ -925,7 +924,7 @@
      */
     @Override
     public void messageDataReceived(ClusterMessage cmsg) {
-        if (cmsg instanceof SessionMessage) {
+        if (cmsg != null && cmsg instanceof SessionMessage) {
             SessionMessage msg = (SessionMessage) cmsg;
             switch (msg.getEventType()) {
                 case SessionMessage.EVT_GET_ALL_SESSIONS:
diff --git a/java/org/apache/catalina/ha/session/DeltaRequest.java b/java/org/apache/catalina/ha/session/DeltaRequest.java
index 04f0be2..1ccd999 100644
--- a/java/org/apache/catalina/ha/session/DeltaRequest.java
+++ b/java/org/apache/catalina/ha/session/DeltaRequest.java
@@ -33,19 +33,19 @@
 
 import org.apache.catalina.SessionListener;
 import org.apache.catalina.realm.GenericPrincipal;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 
 public class DeltaRequest implements Externalizable {
 
-    public static final Log log = LogFactory.getLog(DeltaRequest.class);
+    public static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( DeltaRequest.class );
 
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(DeltaRequest.class);
+    protected static final StringManager sm = StringManager
+            .getManager(Constants.Package);
 
     public static final int TYPE_ATTRIBUTE = 0;
     public static final int TYPE_PRINCIPAL = 1;
diff --git a/java/org/apache/catalina/ha/session/DeltaSession.java b/java/org/apache/catalina/ha/session/DeltaSession.java
index 8c6f064..0b29b0e 100644
--- a/java/org/apache/catalina/ha/session/DeltaSession.java
+++ b/java/org/apache/catalina/ha/session/DeltaSession.java
@@ -42,8 +42,6 @@
 import org.apache.catalina.session.StandardSession;
 import org.apache.catalina.tribes.io.ReplicationStream;
 import org.apache.catalina.tribes.tipis.ReplicatedMapEntry;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -53,12 +51,12 @@
  */
 public class DeltaSession extends StandardSession implements Externalizable,ClusterSession,ReplicatedMapEntry {
 
-    public static final Log log = LogFactory.getLog(DeltaSession.class);
+    public static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(DeltaSession.class);
 
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(DeltaSession.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
 
     // ----------------------------------------------------- Instance Variables
 
diff --git a/java/org/apache/catalina/ha/session/JvmRouteBinderValve.java b/java/org/apache/catalina/ha/session/JvmRouteBinderValve.java
index 5bae469..ad02298 100644
--- a/java/org/apache/catalina/ha/session/JvmRouteBinderValve.java
+++ b/java/org/apache/catalina/ha/session/JvmRouteBinderValve.java
@@ -34,8 +34,6 @@
 import org.apache.catalina.session.ManagerBase;
 import org.apache.catalina.session.PersistentManager;
 import org.apache.catalina.valves.ValveBase;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -83,7 +81,8 @@
 public class JvmRouteBinderValve extends ValveBase implements ClusterValve {
 
     /*--Static Variables----------------------------------------*/
-    public static final Log log = LogFactory.getLog(JvmRouteBinderValve.class);
+    public static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory
+            .getLog(JvmRouteBinderValve.class);
 
     //------------------------------------------------------ Constructor
     public JvmRouteBinderValve() {
@@ -100,7 +99,7 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(JvmRouteBinderValve.class);
+    protected static final StringManager sm = StringManager.getManager(Constants.Package);
 
     /**
      * enabled this component
diff --git a/java/org/apache/catalina/ha/tcp/ReplicationValve.java b/java/org/apache/catalina/ha/tcp/ReplicationValve.java
index ad0e251..ff55ed5 100644
--- a/java/org/apache/catalina/ha/tcp/ReplicationValve.java
+++ b/java/org/apache/catalina/ha/tcp/ReplicationValve.java
@@ -42,8 +42,6 @@
 import org.apache.catalina.ha.session.DeltaManager;
 import org.apache.catalina.ha.session.DeltaSession;
 import org.apache.catalina.valves.ValveBase;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -65,7 +63,8 @@
 public class ReplicationValve
     extends ValveBase implements ClusterValve {
 
-    private static final Log log = LogFactory.getLog(ReplicationValve.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( ReplicationValve.class );
 
     // ----------------------------------------------------- Instance Variables
 
diff --git a/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java b/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
index b48ff60..1299de8 100644
--- a/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
+++ b/java/org/apache/catalina/ha/tcp/SimpleTcpCluster.java
@@ -441,7 +441,7 @@
      */
     @Override
     public void removeManager(Manager manager) {
-        if (manager instanceof ClusterManager) {
+        if (manager != null && manager instanceof ClusterManager ) {
             ClusterManager cmgr = (ClusterManager) manager;
             // Notify our interested LifecycleListeners
             fireLifecycleEvent(BEFORE_MANAGERUNREGISTER_EVENT,manager);
@@ -465,7 +465,7 @@
             Context context = manager.getContext() ;
             if(context != null) {
                 Container host = context.getParent();
-                if(host instanceof Host && clusterName!=null &&
+                if(host != null && host instanceof Host && clusterName!=null &&
                         !(clusterName.startsWith(host.getName() +"#"))) {
                     clusterName = host.getName() +"#" + clusterName ;
                 }
diff --git a/java/org/apache/catalina/loader/ParallelWebappClassLoader.java b/java/org/apache/catalina/loader/ParallelWebappClassLoader.java
index 2235229..312ed53 100644
--- a/java/org/apache/catalina/loader/ParallelWebappClassLoader.java
+++ b/java/org/apache/catalina/loader/ParallelWebappClassLoader.java
@@ -17,12 +17,11 @@
 package org.apache.catalina.loader;
 
 import org.apache.catalina.LifecycleException;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 public class ParallelWebappClassLoader extends WebappClassLoaderBase {
 
-    private static final Log log = LogFactory.getLog(ParallelWebappClassLoader.class);
+    private static final org.apache.juli.logging.Log log =
+            org.apache.juli.logging.LogFactory.getLog(ParallelWebappClassLoader.class);
 
     static {
         boolean result = ClassLoader.registerAsParallelCapable();
diff --git a/java/org/apache/catalina/loader/WebappClassLoaderBase.java b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
index 1cca4e0..3f5be87 100644
--- a/java/org/apache/catalina/loader/WebappClassLoaderBase.java
+++ b/java/org/apache/catalina/loader/WebappClassLoaderBase.java
@@ -75,8 +75,6 @@
 import org.apache.catalina.WebResourceRoot;
 import org.apache.catalina.webresources.TomcatURLStreamHandlerFactory;
 import org.apache.juli.WebappProperties;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.InstrumentableClassLoader;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.IntrospectionUtils;
@@ -129,7 +127,8 @@
 public abstract class WebappClassLoaderBase extends URLClassLoader
         implements Lifecycle, InstrumentableClassLoader, WebappProperties {
 
-    private static final Log log = LogFactory.getLog(WebappClassLoaderBase.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(WebappClassLoaderBase.class);
 
     /**
      * List of ThreadGroup names to ignore when scanning for web application
diff --git a/java/org/apache/catalina/loader/WebappLoader.java b/java/org/apache/catalina/loader/WebappLoader.java
index 52aca0e..3d2dc8f 100644
--- a/java/org/apache/catalina/loader/WebappLoader.java
+++ b/java/org/apache/catalina/loader/WebappLoader.java
@@ -39,8 +39,6 @@
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Loader;
 import org.apache.catalina.util.LifecycleMBeanBase;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.modeler.Registry;
 import org.apache.tomcat.util.res.StringManager;
@@ -667,7 +665,8 @@
     }
 
 
-    private static final Log log = LogFactory.getLog(WebappLoader.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( WebappLoader.class );
 
 
     @Override
diff --git a/java/org/apache/catalina/manager/ManagerServlet.java b/java/org/apache/catalina/manager/ManagerServlet.java
index 47ec336..2b9d201 100644
--- a/java/org/apache/catalina/manager/ManagerServlet.java
+++ b/java/org/apache/catalina/manager/ManagerServlet.java
@@ -102,7 +102,7 @@
  *     (fully qualified Java class name), if available.</li>
  * <li><b>/serverinfo</b> - Display system OS and JVM properties.
  * <li><b>/sessions</b> - Deprecated. Use expire.
- * <li><b>/expire?path=/xxx</b> - List session idle time information about the
+ * <li><b>/expire?path=/xxx</b> - List session idle timeinformation about the
  *     web application attached to context path <code>/xxx</code> for this
  *     virtual host.</li>
  * <li><b>/expire?path=/xxx&amp;idle=mm</b> - Expire sessions
diff --git a/java/org/apache/catalina/manager/util/SessionUtils.java b/java/org/apache/catalina/manager/util/SessionUtils.java
index 121c4df..aee4af3 100644
--- a/java/org/apache/catalina/manager/util/SessionUtils.java
+++ b/java/org/apache/catalina/manager/util/SessionUtils.java
@@ -87,17 +87,17 @@
             // First search "known locations"
             for (int i = 0; i < LOCALE_TEST_ATTRIBUTES.length; ++i) {
                 Object obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i]);
-                if (obj instanceof Locale) {
+                if (null != obj && obj instanceof Locale) {
                     locale = (Locale) obj;
                     break;
                 }
                 obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i].toLowerCase(Locale.ENGLISH));
-                if (obj instanceof Locale) {
+                if (null != obj && obj instanceof Locale) {
                     locale = (Locale) obj;
                     break;
                 }
                 obj = in_session.getAttribute(LOCALE_TEST_ATTRIBUTES[i].toUpperCase(Locale.ENGLISH));
-                if (obj instanceof Locale) {
+                if (null != obj && obj instanceof Locale) {
                     locale = (Locale) obj;
                     break;
                 }
@@ -125,7 +125,7 @@
                         if (null != readMethod) {
                             // Call the property getter and return the value
                             Object possibleLocale = readMethod.invoke(probableEngine, (Object[]) null);
-                            if (possibleLocale instanceof Locale) {
+                            if (null != possibleLocale && possibleLocale instanceof Locale) {
                                 locale = (Locale) possibleLocale;
                             }
                         }
@@ -148,7 +148,7 @@
             for (Enumeration<String> enumeration = in_session.getAttributeNames(); enumeration.hasMoreElements();) {
                 String name = enumeration.nextElement();
                 Object obj = in_session.getAttribute(name);
-                if (obj instanceof Locale) {
+                if (null != obj && obj instanceof Locale) {
                     localeArray.add(obj);
                 }
             }
@@ -210,7 +210,7 @@
             for (Enumeration<String> enumeration = httpSession.getAttributeNames(); enumeration.hasMoreElements();) {
                 String name = enumeration.nextElement();
                 Object obj = httpSession.getAttribute(name);
-                if (obj instanceof Principal || obj instanceof Subject) {
+                if (null != obj && (obj instanceof Principal || obj instanceof Subject)) {
                     principalArray.add(obj);
                 }
             }
diff --git a/java/org/apache/catalina/mapper/Mapper.java b/java/org/apache/catalina/mapper/Mapper.java
index 7c04cd4..89cb945 100644
--- a/java/org/apache/catalina/mapper/Mapper.java
+++ b/java/org/apache/catalina/mapper/Mapper.java
@@ -30,8 +30,6 @@
 import org.apache.catalina.WebResource;
 import org.apache.catalina.WebResourceRoot;
 import org.apache.catalina.Wrapper;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.Ascii;
 import org.apache.tomcat.util.buf.CharChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
@@ -46,9 +44,11 @@
 public final class Mapper {
 
 
-    private static final Log log = LogFactory.getLog(Mapper.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(Mapper.class);
 
-    protected static final StringManager sm = StringManager.getManager(Mapper.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Mapper.class.getPackage().getName());
 
     // ----------------------------------------------------- Instance Variables
 
diff --git a/java/org/apache/catalina/mbeans/MBeanFactory.java b/java/org/apache/catalina/mbeans/MBeanFactory.java
index 2f38f1e..eef057e 100644
--- a/java/org/apache/catalina/mbeans/MBeanFactory.java
+++ b/java/org/apache/catalina/mbeans/MBeanFactory.java
@@ -44,8 +44,6 @@
 import org.apache.catalina.session.StandardManager;
 import org.apache.catalina.startup.ContextConfig;
 import org.apache.catalina.startup.HostConfig;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 
@@ -54,7 +52,8 @@
  */
 public class MBeanFactory {
 
-    private static final Log log = LogFactory.getLog(MBeanFactory.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(MBeanFactory.class);
 
     protected static final StringManager sm =
             StringManager.getManager(Constants.Package);
diff --git a/java/org/apache/catalina/security/SecurityConfig.java b/java/org/apache/catalina/security/SecurityConfig.java
index 5fd0b0a..b2292a4 100644
--- a/java/org/apache/catalina/security/SecurityConfig.java
+++ b/java/org/apache/catalina/security/SecurityConfig.java
@@ -19,8 +19,6 @@
 import java.security.Security;
 
 import org.apache.catalina.startup.CatalinaProperties;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * Util class to protect Catalina against package access and insertion.
@@ -30,7 +28,8 @@
 public final class SecurityConfig{
     private static SecurityConfig singleton = null;
 
-    private static final Log log = LogFactory.getLog(SecurityConfig.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityConfig.class );
 
 
     private static final String PACKAGE_ACCESS =  "sun.,"
diff --git a/java/org/apache/catalina/security/SecurityUtil.java b/java/org/apache/catalina/security/SecurityUtil.java
index 7584203..cc2e0b1 100644
--- a/java/org/apache/catalina/security/SecurityUtil.java
+++ b/java/org/apache/catalina/security/SecurityUtil.java
@@ -35,8 +35,6 @@
 import javax.servlet.http.HttpSession;
 
 import org.apache.catalina.Globals;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.res.StringManager;
 /**
@@ -73,7 +71,8 @@
      */
     private static final Map<Class<?>,Method[]> classCache = new ConcurrentHashMap<>();
 
-    private static final Log log = LogFactory.getLog(SecurityUtil.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityUtil.class );
 
     private static final boolean packageDefinitionEnabled =
          (System.getProperty("package.definition") == null &&
diff --git a/java/org/apache/catalina/servlets/CGIServlet.java b/java/org/apache/catalina/servlets/CGIServlet.java
index 421dcef..a91006a 100644
--- a/java/org/apache/catalina/servlets/CGIServlet.java
+++ b/java/org/apache/catalina/servlets/CGIServlet.java
@@ -374,9 +374,10 @@
         Enumeration<String> params = req.getParameterNames();
         while (params.hasMoreElements()) {
             String param = params.nextElement();
-            for (String value : req.getParameterValues(param)) {
-                out.println("<li><b>parameter</b> " + param + " = " + value);
-            }
+            String values[] = req.getParameterValues(param);
+            for (int i = 0; i < values.length; i++)
+                out.println("<li><b>parameter</b> " + param + " = " +
+                               values[i]);
         }
         out.println("<li><b>protocol</b> = " + req.getProtocol());
         out.println("<li><b>remoteAddr</b> = " + req.getRemoteAddr());
@@ -1233,7 +1234,8 @@
             sb.append("</td></tr>");
 
             sb.append("<tr><td>Command Line Params</td><td>");
-            for (String param : cmdLineParameters) {
+            for (int i=0; i < cmdLineParameters.size(); i++) {
+                String param = cmdLineParameters.get(i);
                 sb.append("<p>");
                 sb.append(param);
                 sb.append("</p>");
diff --git a/java/org/apache/catalina/session/ManagerBase.java b/java/org/apache/catalina/session/ManagerBase.java
index c869448..c2c51ad 100644
--- a/java/org/apache/catalina/session/ManagerBase.java
+++ b/java/org/apache/catalina/session/ManagerBase.java
@@ -89,6 +89,17 @@
      */
     protected int maxInactiveInterval = 30 * 60;
 
+
+    protected static final int SESSION_ID_LENGTH_UNSET = -1;
+
+    /**
+     * The session id length of Sessions created by this Manager.
+     * The length should be set directly on the SessionIdGenerator.
+     * Setting it here is deprecated.
+     */
+    protected int sessionIdLength = SESSION_ID_LENGTH_UNSET;
+
+
     /**
      * The Java class name of the secure random number generator class to be
      * used when generating session identifiers. The random number generator
@@ -204,6 +215,25 @@
     // ------------------------------------------------------------- Properties
 
     @Override
+    @Deprecated
+    public Container getContainer() {
+        return getContext();
+    }
+
+
+    @Override
+    @Deprecated
+    public void setContainer(Container container) {
+
+        if (container instanceof Context || container == null) {
+            setContext((Context) container);
+        } else {
+            log.warn(sm.getString("managerBase.container.noop"));
+        }
+    }
+
+
+    @Override
     public Context getContext() {
         return context;
     }
@@ -219,6 +249,8 @@
         Context oldContext = this.context;
         this.context = context;
         support.firePropertyChange("context", oldContext, this.context);
+        // TODO - delete the line below in Tomcat 9 onwards
+        support.firePropertyChange("container", oldContext, this.context);
 
         // Register with the new Context (if any)
         if (this.context != null) {
@@ -296,6 +328,46 @@
 
 
     /**
+     * Gets the session id length (in bytes) of Sessions created by
+     * this Manager.
+     *
+     * @deprecated Use {@link SessionIdGenerator#getSessionIdLength()}.
+     *             This method will be removed in Tomcat 9 onwards.
+     *
+     * @return The session id length
+     */
+    @Override
+    @Deprecated
+    public int getSessionIdLength() {
+
+        return (this.sessionIdLength);
+
+    }
+
+
+    /**
+     * Sets the session id length (in bytes) for Sessions created by this
+     * Manager.
+     *
+     * @deprecated Use {@link SessionIdGenerator#setSessionIdLength(int)}.
+     *             This method will be removed in Tomcat 9 onwards.
+     *
+     * @param idLength The session id length
+     */
+    @Override
+    @Deprecated
+    public void setSessionIdLength(int idLength) {
+
+        int oldSessionIdLength = this.sessionIdLength;
+        this.sessionIdLength = idLength;
+        support.firePropertyChange("sessionIdLength",
+                                   Integer.valueOf(oldSessionIdLength),
+                                   Integer.valueOf(this.sessionIdLength));
+
+    }
+
+
+    /**
      * Gets the session id generator.
      *
      * @return The session id generator
@@ -533,6 +605,9 @@
             setSessionIdGenerator(sessionIdGenerator);
         }
 
+        if (sessionIdLength != SESSION_ID_LENGTH_UNSET) {
+            sessionIdGenerator.setSessionIdLength(sessionIdLength);
+        }
         sessionIdGenerator.setJvmRoute(getJvmRoute());
         if (sessionIdGenerator instanceof SessionIdGeneratorBase) {
             SessionIdGeneratorBase sig = (SessionIdGeneratorBase)sessionIdGenerator;
diff --git a/java/org/apache/catalina/session/PersistentManagerBase.java b/java/org/apache/catalina/session/PersistentManagerBase.java
index ed48c3c..10cc802 100644
--- a/java/org/apache/catalina/session/PersistentManagerBase.java
+++ b/java/org/apache/catalina/session/PersistentManagerBase.java
@@ -424,7 +424,7 @@
             }
         }
         processPersistenceChecks();
-        if (getStore() instanceof StoreBase) {
+        if ((getStore() != null) && (getStore() instanceof StoreBase)) {
             ((StoreBase) getStore()).processExpires();
         }
 
@@ -875,9 +875,8 @@
             }
         }
 
-        if (getStore() instanceof Lifecycle) {
+        if (getStore() != null && getStore() instanceof Lifecycle)
             ((Lifecycle)getStore()).stop();
-        }
 
         // Require a new random number generator if we are restarted
         super.stopInternal();
diff --git a/java/org/apache/catalina/ssi/SSIMediator.java b/java/org/apache/catalina/ssi/SSIMediator.java
index 0c71147..cdf7d24 100644
--- a/java/org/apache/catalina/ssi/SSIMediator.java
+++ b/java/org/apache/catalina/ssi/SSIMediator.java
@@ -25,9 +25,9 @@
 import java.util.Set;
 import java.util.TimeZone;
 
-import org.apache.catalina.util.RequestUtil;
 import org.apache.catalina.util.Strftime;
 import org.apache.catalina.util.URLEncoder;
+import org.apache.tomcat.util.http.HttpMessages;
 
 /**
  * Allows the different SSICommand implementations to share data/talk to each
@@ -298,7 +298,7 @@
         } else if (encoding.equalsIgnoreCase("none")) {
             retVal = value;
         } else if (encoding.equalsIgnoreCase("entity")) {
-            retVal = RequestUtil.filter(value);
+            retVal = HttpMessages.filter(value);
         } else {
             //This shouldn't be possible
             throw new IllegalArgumentException("Unknown encoding: " + encoding);
diff --git a/java/org/apache/catalina/startup/Catalina.java b/java/org/apache/catalina/startup/Catalina.java
index 134fd8b..f73a1ed 100644
--- a/java/org/apache/catalina/startup/Catalina.java
+++ b/java/org/apache/catalina/startup/Catalina.java
@@ -36,8 +36,6 @@
 import org.apache.catalina.Server;
 import org.apache.catalina.security.SecurityConfig;
 import org.apache.juli.ClassLoaderLogManager;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.digester.Digester;
 import org.apache.tomcat.util.digester.Rule;
@@ -815,7 +813,8 @@
     }
 
 
-    private static final Log log = LogFactory.getLog(Catalina.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( Catalina.class );
 
 }
 
diff --git a/java/org/apache/catalina/startup/CatalinaProperties.java b/java/org/apache/catalina/startup/CatalinaProperties.java
index 4bb0555..40bdd25 100644
--- a/java/org/apache/catalina/startup/CatalinaProperties.java
+++ b/java/org/apache/catalina/startup/CatalinaProperties.java
@@ -24,9 +24,6 @@
 import java.util.Enumeration;
 import java.util.Properties;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 
 /**
  * Utility class to read the bootstrap Catalina configuration.
@@ -35,7 +32,8 @@
  */
 public class CatalinaProperties {
 
-    private static final Log log = LogFactory.getLog(CatalinaProperties.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( CatalinaProperties.class );
 
     private static Properties properties = null;
 
diff --git a/java/org/apache/catalina/startup/Tomcat.java b/java/org/apache/catalina/startup/Tomcat.java
index 38ee428..f9f887e 100644
--- a/java/org/apache/catalina/startup/Tomcat.java
+++ b/java/org/apache/catalina/startup/Tomcat.java
@@ -404,6 +404,7 @@
         // and for the use case the speed benefit wouldn't matter.
 
         connector = new Connector("HTTP/1.1");
+        // connector = new Connector("org.apache.coyote.http11.Http11Protocol");
         connector.setPort(port);
         service.addConnector( connector );
         return connector;
@@ -644,7 +645,7 @@
     }
 
     static final String[] silences = new String[] {
-        "org.apache.coyote.http11.Http11NioProtocol",
+        "org.apache.coyote.http11.Http11Protocol",
         "org.apache.catalina.core.StandardService",
         "org.apache.catalina.core.StandardEngine",
         "org.apache.catalina.startup.ContextConfig",
diff --git a/java/org/apache/catalina/startup/UserConfig.java b/java/org/apache/catalina/startup/UserConfig.java
index 4df548d..f9f8e29 100644
--- a/java/org/apache/catalina/startup/UserConfig.java
+++ b/java/org/apache/catalina/startup/UserConfig.java
@@ -32,8 +32,6 @@
 import org.apache.catalina.Lifecycle;
 import org.apache.catalina.LifecycleEvent;
 import org.apache.catalina.LifecycleListener;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 
@@ -50,7 +48,8 @@
     implements LifecycleListener {
 
 
-    private static final Log log = LogFactory.getLog(UserConfig.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UserConfig.class );
 
 
     // ----------------------------------------------------- Instance Variables
diff --git a/java/org/apache/catalina/storeconfig/ManagerSF.java b/java/org/apache/catalina/storeconfig/ManagerSF.java
index f8dc6a1..9f98af7 100644
--- a/java/org/apache/catalina/storeconfig/ManagerSF.java
+++ b/java/org/apache/catalina/storeconfig/ManagerSF.java
@@ -83,7 +83,7 @@
             StoreDescription parentDesc) throws Exception {
         if (aManager instanceof Manager) {
             Manager manager = (Manager) aManager;
-            // Store nested <SessionIdGenerator> element;
+            // Store nested <SessionIdGenerator> element
             SessionIdGenerator sessionIdGenerator = manager.getSessionIdGenerator();
             if (sessionIdGenerator != null) {
                 storeElement(aWriter, indent, sessionIdGenerator);
diff --git a/java/org/apache/catalina/storeconfig/StandardContextSF.java b/java/org/apache/catalina/storeconfig/StandardContextSF.java
index fb73930..5926286 100644
--- a/java/org/apache/catalina/storeconfig/StandardContextSF.java
+++ b/java/org/apache/catalina/storeconfig/StandardContextSF.java
@@ -315,9 +315,9 @@
         File file = new File(System.getProperty("catalina.base"), "conf");
         Container host = context.getParent();
 
-        if (host instanceof Host) {
+        if ((host != null) && (host instanceof Host)) {
             Container engine = host.getParent();
-            if (engine instanceof Engine) {
+            if ((engine != null) && (engine instanceof Engine)) {
                 file = new File(file, engine.getName());
             }
             file = new File(file, host.getName());
diff --git a/java/org/apache/catalina/storeconfig/StoreConfigLifecycleListener.java b/java/org/apache/catalina/storeconfig/StoreConfigLifecycleListener.java
index 2022211..8451dbc 100644
--- a/java/org/apache/catalina/storeconfig/StoreConfigLifecycleListener.java
+++ b/java/org/apache/catalina/storeconfig/StoreConfigLifecycleListener.java
@@ -38,7 +38,8 @@
 public class StoreConfigLifecycleListener implements LifecycleListener {
 
     private static Log log = LogFactory.getLog(StoreConfigLifecycleListener.class);
-    private static StringManager sm = StringManager.getManager(StoreConfigLifecycleListener.class);
+    private static StringManager sm =
+            StringManager.getManager(StoreConfigLifecycleListener.class.getName());
 
     /**
      * The configuration information registry for our managed beans.
diff --git a/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java b/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java
index 92a17e0..02af5be 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java
+++ b/java/org/apache/catalina/tribes/group/interceptors/FragmentationInterceptor.java
@@ -27,8 +27,6 @@
 import org.apache.catalina.tribes.group.ChannelInterceptorBase;
 import org.apache.catalina.tribes.group.InterceptorPayload;
 import org.apache.catalina.tribes.io.XByteBuffer;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  *
@@ -43,7 +41,7 @@
  * @version 1.0
  */
 public class FragmentationInterceptor extends ChannelInterceptorBase {
-    private static final Log log = LogFactory.getLog(FragmentationInterceptor.class);
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( FragmentationInterceptor.class );
 
     protected final HashMap<FragKey, FragCollection> fragpieces = new HashMap<>();
     private int maxSize = 1024*100;
diff --git a/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java b/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
index 3556804..e8bab37 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
+++ b/java/org/apache/catalina/tribes/group/interceptors/TcpFailureDetector.java
@@ -36,8 +36,6 @@
 import org.apache.catalina.tribes.io.XByteBuffer;
 import org.apache.catalina.tribes.membership.Membership;
 import org.apache.catalina.tribes.membership.StaticMember;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * <p>Title: A perfect failure detector </p>
@@ -60,7 +58,7 @@
  */
 public class TcpFailureDetector extends ChannelInterceptorBase {
 
-    private static final Log log = LogFactory.getLog(TcpFailureDetector.class);
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( TcpFailureDetector.class );
 
     protected static final byte[] TCP_FAIL_DETECT = new byte[] {
         79, -89, 115, 72, 121, -126, 67, -55, -97, 111, -119, -128, -95, 91, 7, 20,
diff --git a/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java b/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
index 403e8a2..9a7a9ed 100644
--- a/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
+++ b/java/org/apache/catalina/tribes/group/interceptors/TwoPhaseCommitInterceptor.java
@@ -27,14 +27,12 @@
 import org.apache.catalina.tribes.group.InterceptorPayload;
 import org.apache.catalina.tribes.util.Arrays;
 import org.apache.catalina.tribes.util.UUIDGenerator;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 public class TwoPhaseCommitInterceptor extends ChannelInterceptorBase {
 
     private static final byte[] START_DATA = new byte[] {113, 1, -58, 2, -34, -60, 75, -78, -101, -12, 32, -29, 32, 111, -40, 4};
     private static final byte[] END_DATA = new byte[] {54, -13, 90, 110, 47, -31, 75, -24, -81, -29, 36, 52, -58, 77, -110, 56};
-    private static final Log log = LogFactory.getLog(TwoPhaseCommitInterceptor.class);
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(TwoPhaseCommitInterceptor.class);
 
     protected final HashMap<UniqueId, MapEntry> messages = new HashMap<>();
     protected long expire = 1000 * 60; //one minute expiration
diff --git a/java/org/apache/catalina/tribes/io/XByteBuffer.java b/java/org/apache/catalina/tribes/io/XByteBuffer.java
index c71a5c3..67f85a7 100644
--- a/java/org/apache/catalina/tribes/io/XByteBuffer.java
+++ b/java/org/apache/catalina/tribes/io/XByteBuffer.java
@@ -27,9 +27,6 @@
 import java.nio.ByteBuffer;
 import java.util.concurrent.atomic.AtomicInteger;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * The XByteBuffer provides a dual functionality.
  * One, it stores message bytes and automatically extends the byte buffer if needed.<BR>
@@ -46,9 +43,11 @@
  * <li><b>END_DATA</b>  - 7 bytes - <i>TLF2003</i></li>
  * </ul>
  */
-public class XByteBuffer {
+public class XByteBuffer
+{
 
-    private static final Log log = LogFactory.getLog(XByteBuffer.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( XByteBuffer.class );
 
     /**
      * This is a package header, 7 bytes (FLT2002)
diff --git a/java/org/apache/catalina/tribes/membership/McastService.java b/java/org/apache/catalina/tribes/membership/McastService.java
index 2bc4407..435269e 100644
--- a/java/org/apache/catalina/tribes/membership/McastService.java
+++ b/java/org/apache/catalina/tribes/membership/McastService.java
@@ -33,8 +33,6 @@
 import org.apache.catalina.tribes.util.Arrays;
 import org.apache.catalina.tribes.util.StringManager;
 import org.apache.catalina.tribes.util.UUIDGenerator;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * A <b>membership</b> implementation using simple multicast.
@@ -44,7 +42,8 @@
  */
 public class McastService implements MembershipService,MembershipListener,MessageListener {
 
-    private static final Log log = LogFactory.getLog(McastService.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( McastService.class );
 
     /**
      * The string manager for this package.
diff --git a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
index ba6128b..c5b99d9 100644
--- a/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
+++ b/java/org/apache/catalina/tribes/membership/McastServiceImpl.java
@@ -37,8 +37,6 @@
 import org.apache.catalina.tribes.io.ChannelData;
 import org.apache.catalina.tribes.io.XByteBuffer;
 import org.apache.catalina.tribes.util.ExecutorFactory;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * A <b>membership</b> implementation using simple multicast.
@@ -49,9 +47,10 @@
  * Need to fix this, could use java.nio and only need one thread to send and receive, or
  * just use a timeout on the receive
  */
-public class McastServiceImpl {
-
-    private static final Log log = LogFactory.getLog(McastService.class);
+public class McastServiceImpl
+{
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog( McastService.class );
 
     protected static final int MAX_PACKET_SIZE = 65535;
     /**
diff --git a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
index 8467f41..8f2503a 100644
--- a/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
+++ b/java/org/apache/catalina/tribes/tipis/AbstractReplicatedMap.java
@@ -648,7 +648,7 @@
                 entry.setProxy(false);
                 entry.setBackupNodes(mapmsg.getBackupNodes());
                 entry.setPrimary(mapmsg.getPrimary());
-                if (mapmsg.getValue() instanceof ReplicatedMapEntry ) {
+                if (mapmsg.getValue()!=null && mapmsg.getValue() instanceof ReplicatedMapEntry ) {
                     ((ReplicatedMapEntry)mapmsg.getValue()).setOwner(getMapOwner());
                 }
             } else {
@@ -925,7 +925,7 @@
                     if ( dest!=null && dest.length >0) {
                         getChannel().send(dest, msg, getChannelSendOptions());
                     }
-                    if (entry.getValue() instanceof ReplicatedMapEntry) {
+                    if ( entry.getValue() != null && entry.getValue() instanceof ReplicatedMapEntry ) {
                         ReplicatedMapEntry val = (ReplicatedMapEntry)entry.getValue();
                         val.setOwner(getMapOwner());
                     }
@@ -1164,7 +1164,7 @@
         }
 
         public boolean isValueSerializable() {
-            return (value == null) || (value instanceof Serializable);
+            return (value==null) || (value instanceof Serializable);
         }
 
         public boolean isSerializable() {
diff --git a/java/org/apache/catalina/tribes/transport/bio/BioSender.java b/java/org/apache/catalina/tribes/transport/bio/BioSender.java
index 62f6ac2..d426df4 100644
--- a/java/org/apache/catalina/tribes/transport/bio/BioSender.java
+++ b/java/org/apache/catalina/tribes/transport/bio/BioSender.java
@@ -30,8 +30,6 @@
 import org.apache.catalina.tribes.transport.Constants;
 import org.apache.catalina.tribes.transport.SenderState;
 import org.apache.catalina.tribes.util.StringManager;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * Send cluster messages with only one socket. Ack and keep Alive Handling is
@@ -42,7 +40,7 @@
  */
 public class BioSender extends AbstractSender {
 
-    private static final Log log = LogFactory.getLog(BioSender.class);
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(BioSender.class);
 
     /**
      * The string manager for this package.
diff --git a/java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java b/java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java
index ed92726..4e27370 100644
--- a/java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java
+++ b/java/org/apache/catalina/tribes/transport/bio/util/FastQueue.java
@@ -22,8 +22,6 @@
 import org.apache.catalina.tribes.ChannelMessage;
 import org.apache.catalina.tribes.Member;
 import org.apache.catalina.tribes.group.InterceptorPayload;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 
 
@@ -36,7 +34,7 @@
  */
 public class FastQueue {
 
-    private static final Log log = LogFactory.getLog(FastQueue.class);
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog(FastQueue.class);
 
     /**
      * This is the actual queue
diff --git a/java/org/apache/catalina/tribes/transport/nio/NioReceiver.java b/java/org/apache/catalina/tribes/transport/nio/NioReceiver.java
index e428aa8..a6f6a54 100644
--- a/java/org/apache/catalina/tribes/transport/nio/NioReceiver.java
+++ b/java/org/apache/catalina/tribes/transport/nio/NioReceiver.java
@@ -114,7 +114,12 @@
         // Get the associated ServerSocket to bind it with
         ServerSocket serverSocket = serverChannel.socket();
         // create a new Selector for use below
-        this.selector.set(Selector.open());
+        synchronized (Selector.class) {
+            // Selector.open() isn't thread safe
+            // http://bugs.sun.com/view_bug.do?bug_id=6427854
+            // Affects 1.6.0_29, fixed in 1.7.0_01
+            this.selector.set(Selector.open());
+        }
         // set the port the server channel will listen to
         //serverSocket.bind(new InetSocketAddress(getBind(), getTcpListenPort()));
         bind(serverSocket,getPort(),getAutoBind());
diff --git a/java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java b/java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java
index 318f734..1dd54d6 100644
--- a/java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java
+++ b/java/org/apache/catalina/tribes/transport/nio/NioReplicationTask.java
@@ -37,8 +37,6 @@
 import org.apache.catalina.tribes.transport.AbstractRxTask;
 import org.apache.catalina.tribes.transport.Constants;
 import org.apache.catalina.tribes.util.Logs;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * A worker thread class which can drain channels and echo-back the input. Each
@@ -52,7 +50,7 @@
  */
 public class NioReplicationTask extends AbstractRxTask {
 
-    private static final Log log = LogFactory.getLog(NioReplicationTask.class);
+    private static final org.apache.juli.logging.Log log = org.apache.juli.logging.LogFactory.getLog( NioReplicationTask.class );
 
     private ByteBuffer buffer = null;
     private SelectionKey key;
diff --git a/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java b/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java
index 70b905e..37e6acc 100644
--- a/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java
+++ b/java/org/apache/catalina/tribes/transport/nio/ParallelNioSender.java
@@ -47,7 +47,12 @@
     protected final HashMap<Member, NioSender> nioSenders = new HashMap<>();
 
     public ParallelNioSender() throws IOException {
-        selector = Selector.open();
+        synchronized (Selector.class) {
+            // Selector.open() isn't thread safe
+            // http://bugs.sun.com/view_bug.do?bug_id=6427854
+            // Affects 1.6.0_29, fixed in 1.7.0_01
+            selector = Selector.open();
+        }
         setConnected(true);
     }
 
diff --git a/java/org/apache/catalina/util/Conversions.java b/java/org/apache/catalina/util/Conversions.java
new file mode 100644
index 0000000..322fdbb
--- /dev/null
+++ b/java/org/apache/catalina/util/Conversions.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.util;
+
+import java.io.IOException;
+
+public class Conversions {
+
+    private Conversions() {
+        // Utility class. Hide default constructor.
+    }
+
+    public static long byteArrayToLong(byte[] input) throws IOException {
+        if (input.length > 8) {
+            // TODO: Better message
+            throw new IOException();
+        }
+
+        int shift = 0;
+        long result = 0;
+        for (int i = input.length - 1; i >= 0; i--) {
+            result = result + ((input[i] & 0xFF) << shift);
+            shift += 8;
+        }
+
+        return result;
+    }
+}
diff --git a/java/org/apache/catalina/util/ExtensionValidator.java b/java/org/apache/catalina/util/ExtensionValidator.java
index e098a74..cf97d48 100644
--- a/java/org/apache/catalina/util/ExtensionValidator.java
+++ b/java/org/apache/catalina/util/ExtensionValidator.java
@@ -30,8 +30,6 @@
 import org.apache.catalina.Context;
 import org.apache.catalina.WebResource;
 import org.apache.catalina.WebResourceRoot;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 
@@ -48,7 +46,8 @@
  */
 public final class ExtensionValidator {
 
-    private static final Log log = LogFactory.getLog(ExtensionValidator.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(ExtensionValidator.class);
 
     /**
      * The string resources for this package.
diff --git a/java/org/apache/catalina/util/SessionIdGeneratorBase.java b/java/org/apache/catalina/util/SessionIdGeneratorBase.java
index f0e1282..2c84c07 100644
--- a/java/org/apache/catalina/util/SessionIdGeneratorBase.java
+++ b/java/org/apache/catalina/util/SessionIdGeneratorBase.java
@@ -48,10 +48,36 @@
      */
     private final Queue<SecureRandom> randoms = new ConcurrentLinkedQueue<>();
 
+
+    /**
+     * The Java class name of the secure random number generator class to be
+     * used when generating session identifiers. The random number generator
+     * class must be self-seeding and have a zero-argument constructor. If not
+     * specified, an instance of {@link SecureRandom} will be generated.
+     */
     private String secureRandomClass = null;
 
+
+    /**
+     * The name of the algorithm to use to create instances of
+     * {@link SecureRandom} which are used to generate session IDs. If no
+     * algorithm is specified, SHA1PRNG is used. To use the platform default
+     * (which may be SHA1PRNG), specify the empty string. If an invalid
+     * algorithm and/or provider is specified the {@link SecureRandom} instances
+     * will be created using the defaults. If that fails, the {@link
+     * SecureRandom} instances will be created using platform defaults.
+     */
     private String secureRandomAlgorithm = "SHA1PRNG";
 
+
+    /**
+     * The name of the provider to use to create instances of
+     * {@link SecureRandom} which are used to generate session IDs. If
+     * no algorithm is specified the of SHA1PRNG default is used. If an invalid
+     * algorithm and/or provider is specified the {@link SecureRandom} instances
+     * will be created using the defaults. If that fails, the {@link
+     * SecureRandom} instances will be created using platform defaults.
+     */
     private String secureRandomProvider = null;
 
 
@@ -64,21 +90,7 @@
 
 
     /**
-     * Get the class name of the {@link SecureRandom} implementation used to
-     * generate session IDs.
-     *
-     * @return The fully qualified class name. {@code null} indicates that the
-     *         JRE provided {@link SecureRandom} implementation will be used
-     */
-    public String getSecureRandomClass() {
-        return secureRandomClass;
-    }
-
-
-    /**
-     * Specify a non-default {@link SecureRandom} implementation to use. The
-     * implementation must be self-seeding and have a zero-argument constructor.
-     * If not specified, an instance of {@link SecureRandom} will be generated.
+     * Specify a non-default @{link {@link SecureRandom} implementation to use.
      *
      * @param secureRandomClass The fully-qualified class name
      */
@@ -88,26 +100,7 @@
 
 
     /**
-     * Get the name of the algorithm used to create the {@link SecureRandom}
-     * instances which generate new session IDs.
-     *
-     * @return The name of the algorithm. {@code null} or the empty string means
-     *         that platform default will be used
-     */
-    public String getSecureRandomAlgorithm() {
-        return secureRandomAlgorithm;
-    }
-
-
-    /**
-     * Specify a non-default algorithm to use to create instances of
-     * {@link SecureRandom} which are used to generate session IDs. If no
-     * algorithm is specified, SHA1PRNG is used. To use the platform default
-     * (which may be SHA1PRNG), specify {@code null} or the empty string. If an
-     * invalid algorithm and/or provider is specified the {@link SecureRandom}
-     * instances will be created using the defaults for this
-     * {@link SessionIdGenerator} implementation. If that fails, the
-     * {@link SecureRandom} instances will be created using platform defaults.
+     * Specify a non-default algorithm to use to generate random numbers.
      *
      * @param secureRandomAlgorithm The name of the algorithm
      */
@@ -117,26 +110,7 @@
 
 
     /**
-     * Get the name of the provider used to create the {@link SecureRandom}
-     * instances which generate new session IDs.
-     *
-     * @return The name of the provider. {@code null} or the empty string means
-     *         that platform default will be used
-     */
-    public String getSecureRandomProvider() {
-        return secureRandomProvider;
-    }
-
-
-    /**
-     * Specify a non-default provider to use to create instances of
-     * {@link SecureRandom} which are used to generate session IDs.  If no
-     * provider is specified, the platform default is used. To use the platform
-     * default specify {@code null} or the empty string. If an invalid algorithm
-     * and/or provider is specified the {@link SecureRandom} instances will be
-     * created using the defaults for this {@link SessionIdGenerator}
-     * implementation. If that fails, the {@link SecureRandom} instances will be
-     * created using platform defaults.
+     * Specify a non-default provider to use to generate random numbers.
      *
      * @param secureRandomProvider  The name of the provider
      */
@@ -174,8 +148,6 @@
     public int getSessionIdLength() {
         return sessionIdLength;
     }
-
-
     /**
      * Specify the number of bytes for a session ID
      *
@@ -195,7 +167,6 @@
         return generateSessionId(jvmRoute);
     }
 
-
     protected void getRandomBytes(byte bytes[]) {
 
         SecureRandom random = randoms.poll();
diff --git a/java/org/apache/catalina/util/StringParser.java b/java/org/apache/catalina/util/StringParser.java
new file mode 100644
index 0000000..1a87da8
--- /dev/null
+++ b/java/org/apache/catalina/util/StringParser.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.util;
+
+
+/**
+ * Utility class for string parsing that is higher performance than
+ * StringParser for simple delimited text cases.  Parsing is performed
+ * by setting the string, and then using the <code>findXxxx()</code> and
+ * <code>skipXxxx()</code> families of methods to remember significant
+ * offsets.  To retrieve the parsed substrings, call the <code>extract()</code>
+ * method with the appropriate saved offset values.
+ *
+ * @author Craig R. McClanahan
+ *
+ * @deprecated Unused. Will be removed in Tomcat 9 onwards.
+ */
+@Deprecated
+public final class StringParser {
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Construct a string parser with no preset string to be parsed.
+     */
+    public StringParser() {
+
+        this(null);
+
+    }
+
+
+    /**
+     * Construct a string parser that is initialized to parse the specified
+     * string.
+     *
+     * @param string The string to be parsed
+     */
+    public StringParser(String string) {
+
+        super();
+        setString(string);
+
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * The characters of the current string, as a character array.  Stored
+     * when the string is first specified to speed up access to characters
+     * being compared during parsing.
+     */
+    private char chars[] = null;
+
+
+    /**
+     * The zero-relative index of the current point at which we are
+     * positioned within the string being parsed.  <strong>NOTE</strong>:
+     * the value of this index can be one larger than the index of the last
+     * character of the string (i.e. equal to the string length) if you
+     * parse off the end of the string.  This value is useful for extracting
+     * substrings that include the end of the string.
+     */
+    private int index = 0;
+
+
+    /**
+     * The length of the String we are currently parsing.  Stored when the
+     * string is first specified to avoid repeated recalculations.
+     */
+    private int length = 0;
+
+
+    /**
+     * The String we are currently parsing.
+     */
+    private String string = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the zero-relative index of our current parsing position
+     * within the string being parsed.
+     */
+    public int getIndex() {
+
+        return (this.index);
+
+    }
+
+
+    /**
+     * Return the length of the string we are parsing.
+     */
+    public int getLength() {
+        return length;
+    }
+
+
+    /**
+     * Set the String we are currently parsing.  The parser state is also reset
+     * to begin at the start of this string.
+     *
+     * @param string The string to be parsed.
+     */
+    public void setString(String string) {
+
+        this.string = string;
+        if (string != null) {
+            this.length = string.length();
+            chars = this.string.toCharArray();
+        } else {
+            this.length = 0;
+            chars = new char[0];
+        }
+        reset();
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Advance the current parsing position by one, if we are not already
+     * past the end of the string.
+     */
+    public void advance() {
+
+        if (index < length)
+            index++;
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and ends at the character before the specified position.  If this is
+     * not possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     * @param end Ending index, zero relative, exclusive
+     */
+    public String extract(int start, int end) {
+
+        if ((start < 0) || (start >= end) || (end > length))
+            return ("");
+        else
+            return (string.substring(start, end));
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of the specified character,
+     * or the index of the character after the last position of the string
+     * if no more occurrences of this character are found.  The current
+     * parsing position is updated to the returned value.
+     *
+     * @param ch Character to be found
+     */
+    public int findChar(char ch) {
+
+        while ((index < length) && (ch != chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Reset the current state of the parser to the beginning of the
+     * current string being parsed.
+     */
+    public void reset() {
+        index = 0;
+    }
+}
diff --git a/java/org/apache/catalina/valves/CometConnectionManagerValve.java b/java/org/apache/catalina/valves/CometConnectionManagerValve.java
new file mode 100644
index 0000000..1d9b82d
--- /dev/null
+++ b/java/org/apache/catalina/valves/CometConnectionManagerValve.java
@@ -0,0 +1,343 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.valves;
+
+
+import java.io.IOException;
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpSession;
+import javax.servlet.http.HttpSessionEvent;
+import javax.servlet.http.HttpSessionListener;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.LifecycleState;
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometProcessor;
+import org.apache.catalina.connector.CometEventImpl;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+
+
+/**
+ * <p>Implementation of a Valve that tracks Comet connections, and closes them
+ * when the associated session expires or the webapp is reloaded.</p>
+ *
+ * <p>This Valve should be attached to a Context.</p>
+ *
+ * @author Remy Maucherat
+ */
+public class CometConnectionManagerValve extends ValveBase
+    implements HttpSessionListener, LifecycleListener {
+
+    //------------------------------------------------------ Constructor
+    public CometConnectionManagerValve() {
+        super(false);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * List of current Comet connections.
+     */
+    protected final List<Request> cometRequests =
+        Collections.synchronizedList(new ArrayList<Request>());
+
+
+    /**
+     * Name of session attribute used to store list of comet connections.
+     */
+    protected final String cometRequestsAttribute =
+        "org.apache.tomcat.comet.connectionList";
+
+
+    /**
+     * Start this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#startInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void startInternal() throws LifecycleException {
+
+        if (container instanceof Context) {
+            container.addLifecycleListener(this);
+        }
+
+        setState(LifecycleState.STARTING);
+    }
+
+
+    /**
+     * Stop this component and implement the requirements
+     * of {@link org.apache.catalina.util.LifecycleBase#stopInternal()}.
+     *
+     * @exception LifecycleException if this component detects a fatal error
+     *  that prevents this component from being used
+     */
+    @Override
+    protected synchronized void stopInternal() throws LifecycleException {
+
+        setState(LifecycleState.STOPPING);
+
+        if (container instanceof Context) {
+            container.removeLifecycleListener(this);
+        }
+    }
+
+
+    @Override
+    public void lifecycleEvent(LifecycleEvent event) {
+        if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType())) {
+            // The container is getting stopped, close all current connections
+            Iterator<Request> iterator = cometRequests.iterator();
+            while (iterator.hasNext()) {
+                Request request = iterator.next();
+                // Remove the session tracking attribute as it isn't
+                // serializable or required.
+                HttpSession session = request.getSession(false);
+                if (session != null) {
+                    session.removeAttribute(cometRequestsAttribute);
+                }
+                // Close the comet connection
+                CometEventImpl cometEvent = request.getEvent();
+                try {
+                    cometEvent.setEventType(CometEvent.EventType.END);
+                    cometEvent.setEventSubType(
+                            CometEvent.EventSubType.WEBAPP_RELOAD);
+                    getNext().event(request, request.getResponse(), cometEvent);
+                } catch (Exception e) {
+                    container.getLogger().warn(
+                            sm.getString("cometConnectionManagerValve.event"),
+                            e);
+                } finally {
+                    try {
+                        cometEvent.close();
+                    } catch (IOException e) {
+                        container.getLogger().warn(sm.getString(
+                                "cometConnectionManagerValve.event"), e);
+                    }
+                }
+            }
+            cometRequests.clear();
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    /**
+     * Register requests for tracking, whenever needed.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void invoke(Request request, Response response)
+        throws IOException, ServletException {
+        // Perform the request
+        getNext().invoke(request, response);
+
+        if (request.isComet() && !response.isClosed()) {
+            // Start tracking this connection, since this is a
+            // begin event, and Comet mode is on
+            HttpSession session = request.getSession(true);
+
+            // Track the connection for webapp reload
+            cometRequests.add(request);
+
+            // Track the connection for session expiration
+            synchronized (session) {
+                ConnectionList list = (ConnectionList) session.getAttribute(
+                        cometRequestsAttribute);
+                Request[] requests = null;
+                if (list != null) {
+                    requests = list.get();
+                }
+                if (requests == null) {
+                    requests = new Request[1];
+                    requests[0] = request;
+                    session.setAttribute(cometRequestsAttribute,
+                            new ConnectionList(requests));
+                } else {
+                    Request[] newRequests =
+                        new Request[requests.length + 1];
+                    for (int i = 0; i < requests.length; i++) {
+                        newRequests[i] = requests[i];
+                    }
+                    newRequests[requests.length] = request;
+                    session.setAttribute(cometRequestsAttribute,
+                            new ConnectionList(newRequests));
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Use events to update the connection state.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs
+     * @exception ServletException if a servlet error occurs
+     */
+    @Override
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+
+        // Perform the request
+        boolean ok = false;
+        try {
+            getNext().event(request, response, event);
+            ok = true;
+        } finally {
+            if (!ok || response.isClosed()
+                    || (event.getEventType() == CometEvent.EventType.END)
+                    || (event.getEventType() == CometEvent.EventType.ERROR
+                            && !(event.getEventSubType() ==
+                                CometEvent.EventSubType.TIMEOUT))) {
+
+                // Remove the connection from webapp reload tracking
+                cometRequests.remove(request);
+
+                // Remove connection from session expiration tracking
+                // Note: can't get the session if it has been invalidated but
+                // OK since session listener will have done clean-up
+                HttpSession session = request.getSession(false);
+                if (session != null) {
+                    synchronized (session) {
+                        Request[] reqs = null;
+                        try {
+                            ConnectionList list =
+                                    (ConnectionList) session.getAttribute(
+                                            cometRequestsAttribute);
+                            if (list != null) {
+                                reqs = list.get();
+                            }
+                        } catch (IllegalStateException ise) {
+                            // Ignore - session has been invalidated
+                            // Listener will have cleaned up
+                        }
+                        if (reqs != null) {
+                            boolean found = false;
+                            for (int i = 0; !found && (i < reqs.length); i++) {
+                                found = (reqs[i] == request);
+                            }
+                            if (found) {
+                                if (reqs.length > 1) {
+                                    Request[] newConnectionInfos =
+                                        new Request[reqs.length - 1];
+                                    int pos = 0;
+                                    for (int i = 0; i < reqs.length; i++) {
+                                        if (reqs[i] != request) {
+                                            newConnectionInfos[pos++] = reqs[i];
+                                        }
+                                    }
+                                    try {
+                                        session.setAttribute(
+                                                cometRequestsAttribute,
+                                                new ConnectionList(
+                                                        newConnectionInfos));
+                                    } catch (IllegalStateException ise) {
+                                        // Ignore - session has been invalidated
+                                        // Listener will have cleaned up
+                                    }
+                                } else {
+                                    try {
+                                        session.removeAttribute(
+                                                cometRequestsAttribute);
+                                    } catch (IllegalStateException ise) {
+                                        // Ignore - session has been invalidated
+                                        // Listener will have cleaned up
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+    }
+
+
+    @Override
+    public void sessionCreated(HttpSessionEvent se) {
+        // NOOP
+    }
+
+
+    @Override
+    public void sessionDestroyed(HttpSessionEvent se) {
+        // Close all Comet connections associated with this session
+        ConnectionList list = (ConnectionList) se.getSession().getAttribute(
+                cometRequestsAttribute);
+        Request[] reqs = null;
+        if (list != null) {
+            reqs = list.get();
+        }
+        if (reqs != null) {
+            for (int i = 0; i < reqs.length; i++) {
+                Request req = reqs[i];
+                try {
+                    CometEventImpl event = req.getEvent();
+                    event.setEventType(CometEvent.EventType.END);
+                    event.setEventSubType(CometEvent.EventSubType.SESSION_END);
+                    ((CometProcessor)
+                            req.getWrapper().getServlet()).event(event);
+                    event.close();
+                } catch (Exception e) {
+                    req.getWrapper().getParent().getLogger().warn(sm.getString(
+                            "cometConnectionManagerValve.listenerEvent"), e);
+                }
+            }
+        }
+    }
+
+
+    private static class ConnectionList implements Serializable {
+
+        private static final long serialVersionUID = 1L;
+
+        private transient Request[] connectionList = null;
+
+        private ConnectionList(Request[] connectionList){
+            this.connectionList = connectionList;
+        }
+
+        public Request[] get(){
+            return connectionList;
+        }
+    }
+}
diff --git a/java/org/apache/catalina/valves/LocalStrings.properties b/java/org/apache/catalina/valves/LocalStrings.properties
index fbb8da2..c11240d 100644
--- a/java/org/apache/catalina/valves/LocalStrings.properties
+++ b/java/org/apache/catalina/valves/LocalStrings.properties
@@ -14,6 +14,8 @@
 # limitations under the License.
 
 jdbcAccessLogValve.exception=Exception performing insert access entry
+cometConnectionManagerValve.event=Exception processing event
+cometConnectionManagerValve.listenerEvent=Exception processing session listener event
 
 # Access log valve
 accessLogValve.openFail=Failed to open access log file [{0}]
diff --git a/java/org/apache/catalina/valves/LocalStrings_es.properties b/java/org/apache/catalina/valves/LocalStrings_es.properties
index 412d12a..bba69a5 100644
--- a/java/org/apache/catalina/valves/LocalStrings_es.properties
+++ b/java/org/apache/catalina/valves/LocalStrings_es.properties
@@ -13,6 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 jdbcAccessLogValve.exception = Excepci\u00F3n realizando entrada de acceso a inserci\u00F3n
+cometConnectionManagerValve.event = Excepci\u00F3n procesando evento
+cometConnectionManagerValve.listenerEvent = Excepci\u00F3n procesando evento de oyente de sesi\u00F3n
 accessLogValve.closeFail = No pude cerrar fichero de historial
 accessLogValve.openDirFail = No pude crear directorio [{0}] para historiales de acceso
 accessLogValve.rotateFail = No pude rotar historial de acceso
diff --git a/java/org/apache/catalina/valves/ValveBase.java b/java/org/apache/catalina/valves/ValveBase.java
index 0603f70..fcf0923 100644
--- a/java/org/apache/catalina/valves/ValveBase.java
+++ b/java/org/apache/catalina/valves/ValveBase.java
@@ -16,6 +16,7 @@
  */
 package org.apache.catalina.valves;
 
+
 import java.io.IOException;
 
 import javax.servlet.ServletException;
@@ -26,12 +27,14 @@
 import org.apache.catalina.LifecycleState;
 import org.apache.catalina.Pipeline;
 import org.apache.catalina.Valve;
+import org.apache.catalina.comet.CometEvent;
 import org.apache.catalina.connector.Request;
 import org.apache.catalina.connector.Response;
 import org.apache.catalina.util.LifecycleMBeanBase;
 import org.apache.juli.logging.Log;
 import org.apache.tomcat.util.res.StringManager;
 
+
 /**
  * Convenience base class for implementations of the <b>Valve</b> interface.
  * A subclass <strong>MUST</strong> implement an <code>invoke()</code>
@@ -179,6 +182,27 @@
         throws IOException, ServletException;
 
 
+    /**
+     * Process a Comet event. This method will rarely need to be provided by
+     * a subclass, unless it needs to reassociate a particular object with
+     * the thread that is processing the request.
+     *
+     * @param request The servlet request to be processed
+     * @param response The servlet response to be created
+     *
+     * @exception IOException if an input/output error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     * @exception ServletException if a servlet error occurs, or is thrown
+     *  by a subsequently invoked Valve, Filter, or Servlet
+     */
+    @Override
+    public void event(Request request, Response response, CometEvent event)
+        throws IOException, ServletException {
+        // Perform the request
+        getNext().event(request, response, event);
+    }
+
+
     @Override
     protected void initInternal() throws LifecycleException {
         super.initInternal();
diff --git a/java/org/apache/catalina/webresources/StandardRoot.java b/java/org/apache/catalina/webresources/StandardRoot.java
index 5965cad..33392c6 100644
--- a/java/org/apache/catalina/webresources/StandardRoot.java
+++ b/java/org/apache/catalina/webresources/StandardRoot.java
@@ -360,8 +360,6 @@
         return result;
     }
 
-    // TODO: Should the createWebResourceSet() methods be removed to some
-    //       utility class for fiel system based resource sets?
 
     @Override
     public void createWebResourceSet(ResourceSetType type, String webAppMount,
diff --git a/java/org/apache/coyote/AbstractProcessor.java b/java/org/apache/coyote/AbstractProcessor.java
index 9dcfad6..762cc5f 100644
--- a/java/org/apache/coyote/AbstractProcessor.java
+++ b/java/org/apache/coyote/AbstractProcessor.java
@@ -25,7 +25,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -41,7 +41,7 @@
     protected final AbstractEndpoint<S> endpoint;
     protected final Request request;
     protected final Response response;
-    protected SocketWrapperBase<S> socketWrapper = null;
+    protected SocketWrapper<S> socketWrapper = null;
 
     /**
      * Error state for the request/response currently being processed.
@@ -140,7 +140,7 @@
     /**
      * Set the socket wrapper being used.
      */
-    protected final void setSocketWrapper(SocketWrapperBase<S> socketWrapper) {
+    protected final void setSocketWrapper(SocketWrapper<S> socketWrapper) {
         this.socketWrapper = socketWrapper;
     }
 
@@ -148,7 +148,7 @@
     /**
      * Get the socket wrapper being used.
      */
-    protected final SocketWrapperBase<S> getSocketWrapper() {
+    protected final SocketWrapper<S> getSocketWrapper() {
         return socketWrapper;
     }
 
@@ -179,6 +179,9 @@
     }
 
     @Override
+    public abstract boolean isComet();
+
+    @Override
     public abstract boolean isUpgrade();
 
     /**
@@ -186,7 +189,13 @@
      * with although they may change type during processing.
      */
     @Override
-    public abstract SocketState process(SocketWrapperBase<S> socket) throws IOException;
+    public abstract SocketState process(SocketWrapper<S> socket) throws IOException;
+
+    /**
+     * Process in-progress Comet requests. These will start as HTTP requests.
+     */
+    @Override
+    public abstract SocketState event(SocketStatus status) throws IOException;
 
     /**
      * Process in-progress Servlet 3.0 Async requests. These will start as HTTP
diff --git a/java/org/apache/coyote/AbstractProtocol.java b/java/org/apache/coyote/AbstractProtocol.java
index 01de109..b7ec2af 100644
--- a/java/org/apache/coyote/AbstractProtocol.java
+++ b/java/org/apache/coyote/AbstractProtocol.java
@@ -42,7 +42,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler;
 import org.apache.tomcat.util.net.DispatchType;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
 public abstract class AbstractProtocol<S> implements ProtocolHandler,
@@ -84,19 +84,10 @@
 
     /**
      * Endpoint that provides low-level network I/O - must be matched to the
-     * ProtocolHandler implementation (ProtocolHandler using NIO, requires NIO
+     * ProtocolHandler implementation (ProtocolHandler using BIO, requires BIO
      * Endpoint etc.).
      */
-    private final AbstractEndpoint<S> endpoint;
-
-    private Handler<S> handler;
-
-
-    public AbstractProtocol(AbstractEndpoint<S> endpoint) {
-        this.endpoint = endpoint;
-        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
-        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
-    }
+    protected AbstractEndpoint<S> endpoint = null;
 
 
     // ----------------------------------------------- Generic property handling
@@ -170,6 +161,18 @@
 
 
     @Override
+    public boolean isCometSupported() {
+        return endpoint.getUseComet();
+    }
+
+
+    @Override
+    public boolean isCometTimeoutSupported() {
+        return endpoint.getUseCometTimeout();
+    }
+
+
+    @Override
     public boolean isSendfileSupported() {
         return endpoint.getUseSendfile();
     }
@@ -315,22 +318,6 @@
     }
 
 
-    // ----------------------------------------------- Accessors for sub-classes
-
-    protected AbstractEndpoint<S> getEndpoint() {
-        return endpoint;
-    }
-
-
-    protected Handler<S> getHandler() {
-        return handler;
-    }
-
-    protected void setHandler(Handler<S> handler) {
-        this.handler = handler;
-    }
-
-
     // -------------------------------------------------------- Abstract methods
 
     /**
@@ -353,6 +340,12 @@
     protected abstract String getProtocolName();
 
 
+    /**
+     * Obtain the handler associated with the underlying Endpoint
+     */
+    protected abstract Handler getHandler();
+
+
     // ----------------------------------------------------- JMX related methods
 
     protected String domain;
@@ -568,7 +561,7 @@
     // ------------------------------------------- Connection handler base class
 
     protected abstract static class AbstractConnectionHandler<S,P extends Processor<S>>
-            implements AbstractEndpoint.Handler<S> {
+            implements AbstractEndpoint.Handler {
 
         protected abstract Log getLog();
 
@@ -596,8 +589,7 @@
         }
 
 
-        @Override
-        public SocketState process(SocketWrapperBase<S> wrapper,
+        public SocketState process(SocketWrapper<S> wrapper,
                 SocketStatus status) {
             if (wrapper == null) {
                 // Nothing to do. Socket has been closed.
@@ -648,11 +640,16 @@
                             state = processor.asyncDispatch(
                                     nextDispatch.getSocketStatus());
                         }
-                    } else if (status == SocketStatus.DISCONNECT) {
+                    } else if (status == SocketStatus.DISCONNECT &&
+                            !processor.isComet()) {
                         // Do nothing here, just wait for it to get recycled
+                        // Don't do this for Comet we need to generate an end
+                        // event (see BZ 54022)
                     } else if (processor.isAsync() ||
                             state == SocketState.ASYNC_END) {
                         state = processor.asyncDispatch(status);
+                    } else if (processor.isComet()) {
+                        state = processor.event(status);
                     } else if (processor.isUpgrade()) {
                         state = processor.upgradeDispatch(status);
                     } else if (status == SocketStatus.OPEN_WRITE) {
@@ -774,15 +771,15 @@
         }
 
         protected abstract P createProcessor();
-        protected abstract void initSsl(SocketWrapperBase<S> socket,
+        protected abstract void initSsl(SocketWrapper<S> socket,
                 Processor<S> processor);
-        protected abstract void longPoll(SocketWrapperBase<S> socket,
+        protected abstract void longPoll(SocketWrapper<S> socket,
                 Processor<S> processor);
-        protected abstract void release(SocketWrapperBase<S> socket,
+        protected abstract void release(SocketWrapper<S> socket,
                 Processor<S> processor, boolean socketClosing,
                 boolean addToPoller);
         protected abstract Processor<S> createUpgradeProcessor(
-                SocketWrapperBase<S> socket, ByteBuffer leftoverInput,
+                SocketWrapper<S> socket, ByteBuffer leftoverInput,
                 HttpUpgradeHandler httpUpgradeProcessor) throws IOException;
 
         protected void register(AbstractProcessor<S> processor) {
diff --git a/java/org/apache/coyote/ActionCode.java b/java/org/apache/coyote/ActionCode.java
index 7fdbcc3..efcf4d8 100644
--- a/java/org/apache/coyote/ActionCode.java
+++ b/java/org/apache/coyote/ActionCode.java
@@ -110,11 +110,31 @@
     REQ_SET_BODY_REPLAY,
 
     /**
+     * Callback for begin Comet processing.
+     */
+    COMET_BEGIN,
+
+    /**
+     * Callback for end Comet processing.
+     */
+    COMET_END,
+
+    /**
      * Callback for getting the amount of available bytes.
      */
     AVAILABLE,
 
     /**
+     * Callback for an asynchronous close of the Comet event
+     */
+    COMET_CLOSE,
+
+    /**
+     * Callback for setting the timeout asynchronously
+     */
+    COMET_SETTIMEOUT,
+
+    /**
      * Callback for an async request.
      */
     ASYNC_START,
diff --git a/java/org/apache/coyote/Adapter.java b/java/org/apache/coyote/Adapter.java
index ecae1d9..0375593 100644
--- a/java/org/apache/coyote/Adapter.java
+++ b/java/org/apache/coyote/Adapter.java
@@ -63,6 +63,9 @@
      */
     public boolean prepare(Request req, Response res) throws Exception;
 
+    public boolean event(Request req, Response res, SocketStatus status)
+            throws Exception;
+
     public boolean asyncDispatch(Request req,Response res, SocketStatus status)
             throws Exception;
 
diff --git a/java/org/apache/coyote/Constants.java b/java/org/apache/coyote/Constants.java
index 75250a3..b79884b 100644
--- a/java/org/apache/coyote/Constants.java
+++ b/java/org/apache/coyote/Constants.java
@@ -44,9 +44,6 @@
     public static final int STAGE_KEEPALIVE = 6;
     public static final int STAGE_ENDED = 7;
 
-    // Default protocol settings
-    public static final int DEFAULT_CONNECTION_LINGER = -1;
-    public static final boolean DEFAULT_TCP_NO_DELAY = true;
 
     /**
      * Has security been turned on?
@@ -65,6 +62,36 @@
 
     /**
      * The request attribute that is set to the value of {@code Boolean.TRUE}
+     * if connector processing this request supports Comet API.
+     */
+    public static final String COMET_SUPPORTED_ATTR =
+        "org.apache.tomcat.comet.support";
+
+
+    /**
+     * The request attribute that is set to the value of {@code Boolean.TRUE}
+     * if connector processing this request supports setting
+     * per-connection request timeout through Comet API.
+     *
+     * @see org.apache.catalina.comet.CometEvent#setTimeout(int)
+     */
+    public static final String COMET_TIMEOUT_SUPPORTED_ATTR =
+        "org.apache.tomcat.comet.timeout.support";
+
+
+    /**
+     * The request attribute that can be set to a value of type
+     * {@code java.lang.Integer} to specify per-connection request
+     * timeout for Comet API. The value is in milliseconds.
+     *
+     * @see org.apache.catalina.comet.CometEvent#setTimeout(int)
+     */
+    public static final String COMET_TIMEOUT_ATTR =
+        "org.apache.tomcat.comet.timeout";
+
+
+    /**
+     * The request attribute that is set to the value of {@code Boolean.TRUE}
      * if connector processing this request supports use of sendfile.
      */
     public static final String SENDFILE_SUPPORTED_ATTR =
diff --git a/java/org/apache/coyote/Processor.java b/java/org/apache/coyote/Processor.java
index 32dd298..81a1d25 100644
--- a/java/org/apache/coyote/Processor.java
+++ b/java/org/apache/coyote/Processor.java
@@ -25,7 +25,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -34,7 +34,9 @@
 public interface Processor<S> {
     Executor getExecutor();
 
-    SocketState process(SocketWrapperBase<S> socketWrapper) throws IOException;
+    SocketState process(SocketWrapper<S> socketWrapper) throws IOException;
+
+    SocketState event(SocketStatus status) throws IOException;
 
     SocketState asyncDispatch(SocketStatus status);
     SocketState asyncPostProcess();
@@ -44,6 +46,7 @@
 
     void errorDispatch();
 
+    boolean isComet();
     boolean isAsync();
     boolean isUpgrade();
 
diff --git a/java/org/apache/coyote/ProtocolHandler.java b/java/org/apache/coyote/ProtocolHandler.java
index 757c044..ed1d0b8 100644
--- a/java/org/apache/coyote/ProtocolHandler.java
+++ b/java/org/apache/coyote/ProtocolHandler.java
@@ -91,6 +91,18 @@
 
 
     /**
+     * Does this ProtocolHandler support Comet?
+     */
+    public boolean isCometSupported();
+
+
+    /**
+     * Does this ProtocolHandler support Comet timeouts?
+     */
+    public boolean isCometTimeoutSupported();
+
+
+    /**
      * Does this ProtocolHandler support sendfile?
      */
     public boolean isSendfileSupported();
diff --git a/java/org/apache/coyote/RequestGroupInfo.java b/java/org/apache/coyote/RequestGroupInfo.java
index c5568b1..ad5b74b 100644
--- a/java/org/apache/coyote/RequestGroupInfo.java
+++ b/java/org/apache/coyote/RequestGroupInfo.java
@@ -14,6 +14,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+
 package org.apache.coyote;
 
 import java.util.ArrayList;
@@ -50,11 +51,10 @@
     }
 
     public synchronized long getMaxTime() {
-        long maxTime = deadMaxTime;
-        for (RequestInfo rp : processors) {
-            if (maxTime < rp.getMaxTime()) {
-                maxTime=rp.getMaxTime();
-            }
+        long maxTime=deadMaxTime;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
+            if( maxTime < rp.getMaxTime() ) maxTime=rp.getMaxTime();
         }
         return maxTime;
     }
@@ -62,14 +62,16 @@
     // Used to reset the times
     public synchronized void setMaxTime(long maxTime) {
         deadMaxTime = maxTime;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             rp.setMaxTime(maxTime);
         }
     }
 
     public synchronized long getProcessingTime() {
-        long time = deadProcessingTime;
-        for (RequestInfo rp : processors) {
+        long time=deadProcessingTime;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             time += rp.getProcessingTime();
         }
         return time;
@@ -77,14 +79,16 @@
 
     public synchronized void setProcessingTime(long totalTime) {
         deadProcessingTime = totalTime;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             rp.setProcessingTime( totalTime );
         }
     }
 
     public synchronized int getRequestCount() {
-        int requestCount = deadRequestCount;
-        for (RequestInfo rp : processors) {
+        int requestCount=deadRequestCount;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             requestCount += rp.getRequestCount();
         }
         return requestCount;
@@ -92,14 +96,16 @@
 
     public synchronized void setRequestCount(int requestCount) {
         deadRequestCount = requestCount;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             rp.setRequestCount( requestCount );
         }
     }
 
     public synchronized int getErrorCount() {
-        int requestCount = deadErrorCount;
-        for (RequestInfo rp : processors) {
+        int requestCount=deadErrorCount;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             requestCount += rp.getErrorCount();
         }
         return requestCount;
@@ -107,14 +113,16 @@
 
     public synchronized void setErrorCount(int errorCount) {
         deadErrorCount = errorCount;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             rp.setErrorCount( errorCount);
         }
     }
 
     public synchronized long getBytesReceived() {
-        long bytes = deadBytesReceived;
-        for (RequestInfo rp : processors) {
+        long bytes=deadBytesReceived;
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             bytes += rp.getBytesReceived();
         }
         return bytes;
@@ -122,14 +130,16 @@
 
     public synchronized void setBytesReceived(long bytesReceived) {
         deadBytesReceived = bytesReceived;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             rp.setBytesReceived( bytesReceived );
         }
     }
 
     public synchronized long getBytesSent() {
         long bytes=deadBytesSent;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             bytes += rp.getBytesSent();
         }
         return bytes;
@@ -137,7 +147,8 @@
 
     public synchronized void setBytesSent(long bytesSent) {
         deadBytesSent = bytesSent;
-        for (RequestInfo rp : processors) {
+        for( int i=0; i<processors.size(); i++ ) {
+            RequestInfo rp=processors.get( i );
             rp.setBytesSent( bytesSent );
         }
     }
diff --git a/java/org/apache/coyote/Response.java b/java/org/apache/coyote/Response.java
index e5c7362..433873b 100644
--- a/java/org/apache/coyote/Response.java
+++ b/java/org/apache/coyote/Response.java
@@ -132,6 +132,10 @@
         this.req=req;
     }
 
+    public OutputBuffer getOutputBuffer() {
+        return outputBuffer;
+    }
+
 
     public void setOutputBuffer(OutputBuffer outputBuffer) {
         this.outputBuffer = outputBuffer;
diff --git a/java/org/apache/coyote/ajp/AbstractAjpProcessor.java b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
new file mode 100644
index 0000000..d7742a5
--- /dev/null
+++ b/java/org/apache/coyote/ajp/AbstractAjpProcessor.java
@@ -0,0 +1,1783 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.ajp;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.security.NoSuchProviderException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.util.Iterator;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+import javax.servlet.RequestDispatcher;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpUpgradeHandler;
+
+import org.apache.coyote.AbstractProcessor;
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.AsyncContextCallback;
+import org.apache.coyote.ByteBufferHolder;
+import org.apache.coyote.ErrorState;
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Request;
+import org.apache.coyote.RequestInfo;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.HexUtils;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.HttpMessages;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.DispatchType;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+import org.apache.tomcat.util.res.StringManager;
+
+/**
+ * Base class for AJP Processor implementations.
+ */
+public abstract class AbstractAjpProcessor<S> extends AbstractProcessor<S> {
+
+    /**
+     * The string manager for this package.
+     */
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
+
+
+    /**
+     * End message array.
+     */
+    protected static final byte[] endMessageArray;
+    protected static final byte[] endAndCloseMessageArray;
+
+
+    /**
+     * Flush message array.
+     */
+    protected static final byte[] flushMessageArray;
+
+
+    /**
+     * Pong message array.
+     */
+    protected static final byte[] pongMessageArray;
+
+
+    static {
+        // Allocate the end message array
+        AjpMessage endMessage = new AjpMessage(16);
+        endMessage.reset();
+        endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
+        endMessage.appendByte(1);
+        endMessage.end();
+        endMessageArray = new byte[endMessage.getLen()];
+        System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
+                endMessage.getLen());
+
+        // Allocate the end and close message array
+        AjpMessage endAndCloseMessage = new AjpMessage(16);
+        endAndCloseMessage.reset();
+        endAndCloseMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
+        endAndCloseMessage.appendByte(0);
+        endAndCloseMessage.end();
+        endAndCloseMessageArray = new byte[endAndCloseMessage.getLen()];
+        System.arraycopy(endAndCloseMessage.getBuffer(), 0, endAndCloseMessageArray, 0,
+                endAndCloseMessage.getLen());
+
+        // Allocate the flush message array
+        AjpMessage flushMessage = new AjpMessage(16);
+        flushMessage.reset();
+        flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+        flushMessage.appendInt(0);
+        flushMessage.appendByte(0);
+        flushMessage.end();
+        flushMessageArray = new byte[flushMessage.getLen()];
+        System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0,
+                flushMessage.getLen());
+
+        // Allocate the pong message array
+        AjpMessage pongMessage = new AjpMessage(16);
+        pongMessage.reset();
+        pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
+        pongMessage.end();
+        pongMessageArray = new byte[pongMessage.getLen()];
+        System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
+                0, pongMessage.getLen());
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * GetBody message array. Not static like the other message arrays since the
+     * message varies with packetSize and that can vary per connector.
+     */
+    protected final byte[] getBodyMessageArray;
+
+
+    /**
+     * AJP packet size.
+     */
+    private final int outputMaxChunkSize;
+
+    /**
+     * Header message. Note that this header is merely the one used during the
+     * processing of the first message of a "request", so it might not be a
+     * request header. It will stay unchanged during the processing of the whole
+     * request.
+     */
+    protected final AjpMessage requestHeaderMessage;
+
+
+    /**
+     * Message used for response composition.
+     */
+    protected final AjpMessage responseMessage;
+
+
+    /**
+     * Location of next write of the response message (used withnon-blocking
+     * writes when the message may not be written in a single write). Avalue of
+     * -1 indicates that no message has been written to the buffer.
+     */
+    private int responseMsgPos = -1;
+
+
+    /**
+     * Body message.
+     */
+    protected final AjpMessage bodyMessage;
+
+
+    /**
+     * Body message.
+     */
+    protected final MessageBytes bodyBytes = MessageBytes.newInstance();
+
+
+    /**
+     * The max size of the buffered write buffer
+     */
+    private int bufferedWriteSize = 64*1024; //64k default write buffer
+
+
+    /**
+     * For "non-blocking" writes use an external set of buffers. Although the
+     * API only allows one non-blocking write at a time, due to buffering and
+     * the possible need to write HTTP headers, there may be more than one write
+     * to the OutputBuffer.
+     */
+    private final LinkedBlockingDeque<ByteBufferHolder> bufferedWrites =
+            new LinkedBlockingDeque<>();
+
+
+    /**
+     * Host name (used to avoid useless B2C conversion on the host name).
+     */
+    protected char[] hostNameC = new char[0];
+
+
+    /**
+     * Temp message bytes used for processing.
+     */
+    protected final MessageBytes tmpMB = MessageBytes.newInstance();
+
+
+    /**
+     * Byte chunk for certs.
+     */
+    protected final MessageBytes certificates = MessageBytes.newInstance();
+
+
+    /**
+     * End of stream flag.
+     */
+    protected boolean endOfStream = false;
+
+
+    /**
+     * Request body empty flag.
+     */
+    protected boolean empty = true;
+
+
+    /**
+     * First read.
+     */
+    protected boolean first = true;
+
+
+    /**
+     * Indicates that a 'get body chunk' message has been sent but the body
+     * chunk has not yet been received.
+     */
+    private boolean waitingForBodyMessage = false;
+
+
+    /**
+     * Replay read.
+     */
+    protected boolean replay = false;
+
+
+    /**
+     * Should any response body be swallowed and not sent to the client.
+     */
+    private boolean swallowResponse = false;
+
+
+    /**
+     * Finished response.
+     */
+    protected boolean finished = false;
+
+
+    /**
+     * Bytes written to client for the current request.
+     */
+    protected long bytesWritten = 0;
+
+
+    // ------------------------------------------------------------ Constructor
+
+    public AbstractAjpProcessor(int packetSize, AbstractEndpoint<S> endpoint) {
+
+        super(endpoint);
+
+        // Calculate maximum chunk size as packetSize may have been changed from
+        // the default (Constants.MAX_PACKET_SIZE)
+        this.outputMaxChunkSize =
+                Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
+
+        request.setInputBuffer(new SocketInputBuffer());
+
+        requestHeaderMessage = new AjpMessage(packetSize);
+        responseMessage = new AjpMessage(packetSize);
+        bodyMessage = new AjpMessage(packetSize);
+
+        // Set the getBody message buffer
+        AjpMessage getBodyMessage = new AjpMessage(16);
+        getBodyMessage.reset();
+        getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
+        // Adjust read size if packetSize != default (Constants.MAX_PACKET_SIZE)
+        getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize -
+                Constants.MAX_PACKET_SIZE);
+        getBodyMessage.end();
+        getBodyMessageArray = new byte[getBodyMessage.getLen()];
+        System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray,
+                0, getBodyMessage.getLen());
+    }
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * The number of milliseconds Tomcat will wait for a subsequent request
+     * before closing the connection. The default is -1 which is an infinite
+     * timeout.
+     */
+    protected int keepAliveTimeout = -1;
+    public int getKeepAliveTimeout() { return keepAliveTimeout; }
+    public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
+
+
+    /**
+     * Use Tomcat authentication ?
+     */
+    protected boolean tomcatAuthentication = true;
+    public boolean getTomcatAuthentication() { return tomcatAuthentication; }
+    public void setTomcatAuthentication(boolean tomcatAuthentication) {
+        this.tomcatAuthentication = tomcatAuthentication;
+    }
+
+
+    /**
+     * Required secret.
+     */
+    protected String requiredSecret = null;
+    public void setRequiredSecret(String requiredSecret) {
+        this.requiredSecret = requiredSecret;
+    }
+
+
+    /**
+     * When client certificate information is presented in a form other than
+     * instances of {@link java.security.cert.X509Certificate} it needs to be
+     * converted before it can be used and this property controls which JSSE
+     * provider is used to perform the conversion. For example it is used with
+     * the AJP connectors, the HTTP APR connector and with the
+     * {@link org.apache.catalina.valves.SSLValve}. If not specified, the
+     * default provider will be used.
+     */
+    protected String clientCertProvider = null;
+    public String getClientCertProvider() { return clientCertProvider; }
+    public void setClientCertProvider(String s) { this.clientCertProvider = s; }
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @Override
+    public final void action(ActionCode actionCode, Object param) {
+
+        switch (actionCode) {
+        case CLOSE: {
+            // End the processing of the current request, and stop any further
+            // transactions with the client
+
+            try {
+                finish();
+            } catch (IOException e) {
+                setErrorState(ErrorState.CLOSE_NOW, e);
+            }
+            break;
+        }
+        case COMMIT: {
+            if (response.isCommitted())
+                return;
+
+            // Validate and write response headers
+            try {
+                prepareResponse();
+            } catch (IOException e) {
+                setErrorState(ErrorState.CLOSE_NOW, e);
+            }
+
+            try {
+                flush(false);
+            } catch (IOException e) {
+                setErrorState(ErrorState.CLOSE_NOW, e);
+            }
+            break;
+        }
+        case ACK: {
+            // NO_OP for AJP
+            break;
+        }
+        case CLIENT_FLUSH: {
+            if (!response.isCommitted()) {
+                // Validate and write response headers
+                try {
+                    prepareResponse();
+                } catch (IOException e) {
+                    setErrorState(ErrorState.CLOSE_NOW, e);
+                    return;
+                }
+            }
+
+            try {
+                flush(true);
+            } catch (IOException e) {
+                setErrorState(ErrorState.CLOSE_NOW, e);
+            }
+            break;
+        }
+        case IS_ERROR: {
+            ((AtomicBoolean) param).set(getErrorState().isError());
+            break;
+        }
+        case DISABLE_SWALLOW_INPUT: {
+            // TODO: Do not swallow request input but
+            // make sure we are closing the connection
+            setErrorState(ErrorState.CLOSE_CLEAN, null);
+            break;
+        }
+        case RESET: {
+            // NO-OP
+            break;
+        }
+        case REQ_SSL_ATTRIBUTE: {
+            if (!certificates.isNull()) {
+                ByteChunk certData = certificates.getByteChunk();
+                X509Certificate jsseCerts[] = null;
+                ByteArrayInputStream bais =
+                    new ByteArrayInputStream(certData.getBytes(),
+                            certData.getStart(),
+                            certData.getLength());
+                // Fill the  elements.
+                try {
+                    CertificateFactory cf;
+                    if (clientCertProvider == null) {
+                        cf = CertificateFactory.getInstance("X.509");
+                    } else {
+                        cf = CertificateFactory.getInstance("X.509",
+                                clientCertProvider);
+                    }
+                    while(bais.available() > 0) {
+                        X509Certificate cert = (X509Certificate)
+                        cf.generateCertificate(bais);
+                        if(jsseCerts == null) {
+                            jsseCerts = new X509Certificate[1];
+                            jsseCerts[0] = cert;
+                        } else {
+                            X509Certificate [] temp = new X509Certificate[jsseCerts.length+1];
+                            System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length);
+                            temp[jsseCerts.length] = cert;
+                            jsseCerts = temp;
+                        }
+                    }
+                } catch (java.security.cert.CertificateException e) {
+                    getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
+                    return;
+                } catch (NoSuchProviderException e) {
+                    getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
+                    return;
+                }
+                request.setAttribute(SSLSupport.CERTIFICATE_KEY, jsseCerts);
+            }
+            break;
+        }
+        case REQ_SSL_CERTIFICATE: {
+            // NO-OP. Can't force a new SSL handshake with the client when using
+            // AJP as the reverse proxy controls that connection.
+            break;
+        }
+        case REQ_HOST_ATTRIBUTE: {
+            // Get remote host name using a DNS resolution
+            if (request.remoteHost().isNull()) {
+                try {
+                    request.remoteHost().setString(InetAddress.getByName
+                            (request.remoteAddr().toString()).getHostName());
+                } catch (IOException iex) {
+                    // Ignore
+                }
+            }
+            break;
+        }
+        case REQ_HOST_ADDR_ATTRIBUTE: {
+            // NO-OP
+            // Automatically populated during prepareRequest()
+            break;
+        }
+        case REQ_LOCAL_NAME_ATTRIBUTE: {
+            // NO-OP
+            // Automatically populated during prepareRequest()
+            break;
+        }
+        case REQ_LOCAL_ADDR_ATTRIBUTE: {
+            // Automatically populated during prepareRequest() when using
+            // modern AJP forwarder, otherwise copy from local name
+            if (request.localAddr().isNull()) {
+                request.localAddr().setString(request.localName().toString());
+            }
+            break;
+        }
+        case REQ_REMOTEPORT_ATTRIBUTE: {
+            // NO-OP
+            // Automatically populated during prepareRequest() when using
+            // modern AJP forwarder, otherwise not available
+            break;
+        }
+        case REQ_LOCALPORT_ATTRIBUTE: {
+            // NO-OP
+            // Automatically populated during prepareRequest()
+            break;
+        }
+        case REQ_SET_BODY_REPLAY: {
+            // Set the given bytes as the content
+            ByteChunk bc = (ByteChunk) param;
+            int length = bc.getLength();
+            bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length);
+            request.setContentLength(length);
+            first = false;
+            empty = false;
+            replay = true;
+            endOfStream = false;
+            break;
+        }
+        case ASYNC_START: {
+            asyncStateMachine.asyncStart((AsyncContextCallback) param);
+            // Async time out is based on SocketWrapper access time
+            getSocketWrapper().access();
+            break;
+        }
+        case ASYNC_COMPLETE: {
+            socketWrapper.clearDispatches();
+            if (asyncStateMachine.asyncComplete()) {
+                endpoint.processSocket(socketWrapper, SocketStatus.OPEN_READ, true);
+            }
+            break;
+        }
+        case ASYNC_DISPATCH: {
+            if (asyncStateMachine.asyncDispatch()) {
+                endpoint.processSocket(socketWrapper, SocketStatus.OPEN_READ, true);
+            }
+            break;
+        }
+        case ASYNC_DISPATCHED: {
+            asyncStateMachine.asyncDispatched();
+            break;
+        }
+        case ASYNC_SETTIMEOUT: {
+            if (param == null) return;
+            long timeout = ((Long)param).longValue();
+            socketWrapper.setTimeout(timeout);
+            break;
+        }
+        case ASYNC_TIMEOUT: {
+            AtomicBoolean result = (AtomicBoolean) param;
+            result.set(asyncStateMachine.asyncTimeout());
+            break;
+        }
+        case ASYNC_RUN: {
+            asyncStateMachine.asyncRun((Runnable) param);
+            break;
+        }
+        case ASYNC_ERROR: {
+            asyncStateMachine.asyncError();
+            break;
+        }
+        case ASYNC_IS_STARTED: {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncStarted());
+            break;
+        }
+        case ASYNC_IS_COMPLETING: {
+            ((AtomicBoolean) param).set(asyncStateMachine.isCompleting());
+            break;
+        }
+        case ASYNC_IS_DISPATCHING: {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncDispatching());
+            break;
+        }
+        case ASYNC_IS_ASYNC: {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsync());
+            break;
+        }
+        case ASYNC_IS_TIMINGOUT: {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
+            break;
+        }
+        case ASYNC_IS_ERROR: {
+            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncError());
+            break;
+        }
+        case UPGRADE: {
+            // HTTP connections only. Unsupported for AJP.
+            throw new UnsupportedOperationException(
+                    sm.getString("ajpprocessor.httpupgrade.notsupported"));
+        }
+        case COMET_BEGIN: {
+            // HTTP connections only. Unsupported for AJP.
+            throw new UnsupportedOperationException(
+                    sm.getString("ajpprocessor.comet.notsupported"));
+        }
+        case COMET_END: {
+            // HTTP connections only. Unsupported for AJP.
+            throw new UnsupportedOperationException(
+                    sm.getString("ajpprocessor.comet.notsupported"));
+        }
+        case COMET_CLOSE: {
+            // HTTP connections only. Unsupported for AJP.
+            throw new UnsupportedOperationException(
+                    sm.getString("ajpprocessor.comet.notsupported"));
+        }
+        case COMET_SETTIMEOUT: {
+            // HTTP connections only. Unsupported for AJP.
+            throw new UnsupportedOperationException(
+                    sm.getString("ajpprocessor.comet.notsupported"));
+        }
+        case AVAILABLE: {
+            if (available()) {
+                request.setAvailable(1);
+            } else {
+                request.setAvailable(0);
+            }
+            break;
+        }
+        case NB_READ_INTEREST: {
+            if (!endOfStream) {
+                registerForEvent(true, false);
+            }
+            break;
+        }
+        case NB_WRITE_INTEREST: {
+            AtomicBoolean isReady = (AtomicBoolean)param;
+            boolean result = bufferedWrites.size() == 0 && responseMsgPos == -1;
+            isReady.set(result);
+            if (!result) {
+                registerForEvent(false, true);
+            }
+            break;
+        }
+        case REQUEST_BODY_FULLY_READ: {
+            AtomicBoolean result = (AtomicBoolean) param;
+            result.set(endOfStream);
+            break;
+        }
+        case DISPATCH_READ: {
+            socketWrapper.addDispatch(DispatchType.NON_BLOCKING_READ);
+            break;
+        }
+        case DISPATCH_WRITE: {
+            socketWrapper.addDispatch(DispatchType.NON_BLOCKING_WRITE);
+            break;
+        }
+        case DISPATCH_EXECUTE: {
+            getEndpoint().executeNonBlockingDispatches(socketWrapper);
+            break;
+        }
+        case CLOSE_NOW: {
+            // Prevent further writes to the response
+            swallowResponse = true;
+            setErrorState(ErrorState.CLOSE_NOW, null);
+            break;
+        }
+        }
+    }
+
+
+    @Override
+    public SocketState asyncDispatch(SocketStatus status) {
+
+        if (status == SocketStatus.OPEN_WRITE) {
+            try {
+                asyncStateMachine.asyncOperation();
+                try {
+                    if (hasDataToWrite()) {
+                        flushBufferedData();
+                        if (hasDataToWrite()) {
+                            // There is data to write but go via Response to
+                            // maintain a consistent view of non-blocking state
+                            response.checkRegisterForWrite(true);
+                            return SocketState.LONG;
+                        }
+                    }
+                } catch (IOException x) {
+                    if (getLog().isDebugEnabled()) {
+                        getLog().debug("Unable to write async data.",x);
+                    }
+                    status = SocketStatus.ASYNC_WRITE_ERROR;
+                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, x);
+                }
+            } catch (IllegalStateException x) {
+                registerForEvent(false, true);
+            }
+        } else if (status == SocketStatus.OPEN_READ &&
+                request.getReadListener() != null) {
+            try {
+                if (available()) {
+                    asyncStateMachine.asyncOperation();
+                }
+            } catch (IllegalStateException x) {
+                registerForEvent(true, false);
+            }
+        }
+
+        RequestInfo rp = request.getRequestProcessor();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            if(!getAdapter().asyncDispatch(request, response, status)) {
+                setErrorState(ErrorState.CLOSE_NOW, null);
+            }
+            resetTimeouts();
+        } catch (InterruptedIOException e) {
+            setErrorState(ErrorState.CLOSE_NOW, e);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            setErrorState(ErrorState.CLOSE_NOW, t);
+            getLog().error(sm.getString("http11processor.request.process"), t);
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (isAsync()) {
+            if (getErrorState().isError()) {
+                request.updateCounters();
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.LONG;
+            }
+        } else {
+            request.updateCounters();
+            if (getErrorState().isError()) {
+                return SocketState.CLOSED;
+            } else {
+                return SocketState.OPEN;
+            }
+        }
+    }
+
+
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    @Override
+    public SocketState process(SocketWrapper<S> socket) throws IOException {
+
+        RequestInfo rp = request.getRequestProcessor();
+        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
+
+        // Setting up the socket
+        this.socketWrapper = socket;
+
+        setupSocket(socket);
+
+        int soTimeout = endpoint.getSoTimeout();
+        boolean cping = false;
+
+        boolean keptAlive = false;
+
+        while (!getErrorState().isError() && !endpoint.isPaused()) {
+            // Parsing the request header
+            try {
+                // Get first message of the request
+                if (!readMessage(requestHeaderMessage, !keptAlive)) {
+                    break;
+                }
+                // Set back timeout if keep alive timeout is enabled
+                if (keepAliveTimeout > 0) {
+                    setTimeout(socketWrapper, soTimeout);
+                }
+                // Check message type, process right away and break if
+                // not regular request processing
+                int type = requestHeaderMessage.getByte();
+                if (type == Constants.JK_AJP13_CPING_REQUEST) {
+                    if (endpoint.isPaused()) {
+                        recycle(true);
+                        break;
+                    }
+                    cping = true;
+                    try {
+                        output(pongMessageArray, 0, pongMessageArray.length, true);
+                    } catch (IOException e) {
+                        setErrorState(ErrorState.CLOSE_NOW, e);
+                    }
+                    recycle(false);
+                    continue;
+                } else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
+                    // Unexpected packet type. Unread body packets should have
+                    // been swallowed in finish().
+                    if (getLog().isDebugEnabled()) {
+                        getLog().debug("Unexpected message: " + type);
+                    }
+                    setErrorState(ErrorState.CLOSE_NOW, null);
+                    break;
+                }
+                keptAlive = true;
+                request.setStartTime(System.currentTimeMillis());
+            } catch (IOException e) {
+                setErrorState(ErrorState.CLOSE_NOW, e);
+                break;
+            } catch (Throwable t) {
+                ExceptionUtils.handleThrowable(t);
+                getLog().debug(sm.getString("ajpprocessor.header.error"), t);
+                // 400 - Bad Request
+                response.setStatus(400);
+                setErrorState(ErrorState.CLOSE_CLEAN, t);
+                getAdapter().log(request, response, 0);
+            }
+
+            if (!getErrorState().isError()) {
+                // Setting up filters, and parse some request headers
+                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
+                try {
+                    prepareRequest();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLog().debug(sm.getString("ajpprocessor.request.prepare"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    setErrorState(ErrorState.CLOSE_CLEAN, t);
+                    getAdapter().log(request, response, 0);
+                }
+            }
+
+            if (!getErrorState().isError() && !cping && endpoint.isPaused()) {
+                // 503 - Service unavailable
+                response.setStatus(503);
+                setErrorState(ErrorState.CLOSE_CLEAN, null);
+                getAdapter().log(request, response, 0);
+            }
+            cping = false;
+
+            // Process the request in the adapter
+            if (!getErrorState().isError()) {
+                try {
+                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+                    getAdapter().service(request, response);
+                } catch (InterruptedIOException e) {
+                    setErrorState(ErrorState.CLOSE_NOW, e);
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    getLog().error(sm.getString("ajpprocessor.request.process"), t);
+                    // 500 - Internal Server Error
+                    response.setStatus(500);
+                    setErrorState(ErrorState.CLOSE_CLEAN, t);
+                    getAdapter().log(request, response, 0);
+                }
+            }
+
+            if (isAsync() && !getErrorState().isError()) {
+                break;
+            }
+
+            // Finish the response if not done yet
+            if (!finished && getErrorState().isIoAllowed()) {
+                try {
+                    finish();
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    setErrorState(ErrorState.CLOSE_NOW, t);
+                }
+            }
+
+            // If there was an error, make sure the request is counted as
+            // and error, and update the statistics counter
+            if (getErrorState().isError()) {
+                response.setStatus(500);
+            }
+            request.updateCounters();
+
+            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
+            // Set keep alive timeout if enabled
+            if (keepAliveTimeout > 0) {
+                setTimeout(socketWrapper, keepAliveTimeout);
+            }
+
+            recycle(false);
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (getErrorState().isError() || endpoint.isPaused()) {
+            return SocketState.CLOSED;
+        } else {
+            if (isAsync()) {
+                return SocketState.LONG;
+            } else {
+                return SocketState.OPEN;
+            }
+        }
+    }
+
+
+    @Override
+    public void setSslSupport(SSLSupport sslSupport) {
+        // Should never reach this code but in case we do...
+        throw new IllegalStateException(
+                sm.getString("ajpprocessor.ssl.notsupported"));
+    }
+
+
+    @Override
+    public SocketState event(SocketStatus status) throws IOException {
+        // Should never reach this code but in case we do...
+        throw new IOException(
+                sm.getString("ajpprocessor.comet.notsupported"));
+    }
+
+
+    @Override
+    public SocketState upgradeDispatch(SocketStatus status) throws IOException {
+        // Should never reach this code but in case we do...
+        throw new IOException(
+                sm.getString("ajpprocessor.httpupgrade.notsupported"));
+    }
+
+
+    @Override
+    public HttpUpgradeHandler getHttpUpgradeHandler() {
+        // Should never reach this code but in case we do...
+        throw new IllegalStateException(
+                sm.getString("ajpprocessor.httpupgrade.notsupported"));
+    }
+
+
+    /**
+     * Recycle the processor, ready for the next request which may be on the
+     * same connection or a different connection.
+     *
+     * @param socketClosing Indicates if the socket is about to be closed
+     *                      allowing the processor to perform any additional
+     *                      clean-up that may be required
+     */
+    @Override
+    public void recycle(boolean socketClosing) {
+        getAdapter().checkRecycled(request, response);
+
+        asyncStateMachine.recycle();
+
+        // Recycle Request object
+        first = true;
+        endOfStream = false;
+        waitingForBodyMessage = false;
+        empty = true;
+        replay = false;
+        finished = false;
+        request.recycle();
+        response.recycle();
+        certificates.recycle();
+        swallowResponse = false;
+        bytesWritten = 0;
+        resetErrorState();
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+    // Methods called by asyncDispatch
+    /**
+     * Provides a mechanism for those connector implementations (currently only
+     * NIO) that need to reset timeouts from Async timeouts to standard HTTP
+     * timeouts once async processing completes.
+     */
+    protected abstract void resetTimeouts();
+
+    // Methods called by prepareResponse()
+    protected abstract int output(byte[] src, int offset, int length,
+            boolean block) throws IOException;
+
+    // Methods called by process()
+    protected abstract void setupSocket(SocketWrapper<S> socketWrapper)
+            throws IOException;
+
+    protected abstract void setTimeout(SocketWrapper<S> socketWrapper,
+            int timeout) throws IOException;
+
+    // Methods used by readMessage
+    /**
+     * Read at least the specified amount of bytes, and place them
+     * in the input buffer. Note that if any data is available to read then this
+     * method will always block until at least the specified number of bytes
+     * have been read.
+     *
+     * @param buf   Buffer to read data into
+     * @param pos   Start position
+     * @param n     The minimum number of bytes to read
+     * @param block If there is no data available to read when this method is
+     *              called, should this call block until data becomes available?
+     * @return  <code>true</code> if the requested number of bytes were read
+     *          else <code>false</code>
+     * @throws IOException
+     */
+    protected abstract boolean read(byte[] buf, int pos, int n, boolean block)
+            throws IOException;
+
+    // Methods used by SocketInputBuffer
+    /**
+     * Read an AJP body message. Used to read both the 'special' packet in ajp13
+     * and to receive the data after we send a GET_BODY packet.
+     *
+     * @param block If there is no data available to read when this method is
+     *              called, should this call block until data becomes available?
+     *
+     * @return <code>true</code> if at least one body byte was read, otherwise
+     *         <code>false</code>
+     */
+    protected boolean receive(boolean block) throws IOException {
+
+        bodyMessage.reset();
+
+        if (!readMessage(bodyMessage, block)) {
+            return false;
+        }
+
+        waitingForBodyMessage = false;
+
+        // No data received.
+        if (bodyMessage.getLen() == 0) {
+            // just the header
+            return false;
+        }
+        int blen = bodyMessage.peekInt();
+        if (blen == 0) {
+            return false;
+        }
+
+        bodyMessage.getBodyBytes(bodyBytes);
+        empty = false;
+        return true;
+    }
+
+
+    /**
+     * Read an AJP message.
+     *
+     * @param message   The message to populate
+     * @param block If there is no data available to read when this method is
+     *              called, should this call block until data becomes available?
+
+     * @return true if the message has been read, false if no data was read
+     *
+     * @throws IOException any other failure, including incomplete reads
+     */
+    protected boolean readMessage(AjpMessage message, boolean block)
+        throws IOException {
+
+        byte[] buf = message.getBuffer();
+        int headerLength = message.getHeaderLength();
+
+        if (!read(buf, 0, headerLength, block)) {
+            return false;
+        }
+
+        int messageLength = message.processHeader(true);
+        if (messageLength < 0) {
+            // Invalid AJP header signature
+            throw new IOException(sm.getString("ajpmessage.invalidLength",
+                    Integer.valueOf(messageLength)));
+        }
+        else if (messageLength == 0) {
+            // Zero length message.
+            return true;
+        }
+        else {
+            if (messageLength > message.getBuffer().length) {
+                // Message too long for the buffer
+                // Need to trigger a 400 response
+                throw new IllegalArgumentException(sm.getString(
+                        "ajpprocessor.header.tooLong",
+                        Integer.valueOf(messageLength),
+                        Integer.valueOf(buf.length)));
+            }
+            read(buf, headerLength, messageLength, true);
+            return true;
+        }
+    }
+
+
+    @Override
+    public final boolean isComet() {
+        // AJP does not support Comet
+        return false;
+    }
+
+
+    @Override
+    public final boolean isUpgrade() {
+        // AJP does not support HTTP upgrade
+        return false;
+    }
+
+
+    @Override
+    public ByteBuffer getLeftoverInput() {
+        return null;
+    }
+
+
+    /**
+     * Get more request body data from the web server and store it in the
+     * internal buffer.
+     *
+     * @return true if there is more data, false if not.
+     */
+    protected boolean refillReadBuffer(boolean block) throws IOException {
+        // When using replay (e.g. after FORM auth) all the data to read has
+        // been buffered so there is no opportunity to refill the buffer.
+        if (replay) {
+            endOfStream = true; // we've read everything there is
+        }
+        if (endOfStream) {
+            return false;
+        }
+
+        if (first) {
+            first = false;
+            long contentLength = request.getContentLengthLong();
+            // - When content length > 0, AJP sends the first body message
+            //   automatically.
+            // - When content length == 0, AJP does not send a body message.
+            // - When content length is unknown, AJP does not send the first
+            //   body message automatically.
+            if (contentLength > 0) {
+                waitingForBodyMessage = true;
+            } else if (contentLength == 0) {
+                endOfStream = true;
+                return false;
+            }
+        }
+
+        // Request more data immediately
+        if (!waitingForBodyMessage) {
+            output(getBodyMessageArray, 0, getBodyMessageArray.length, true);
+            waitingForBodyMessage = true;
+        }
+
+        boolean moreData = receive(block);
+        if (!moreData && !waitingForBodyMessage) {
+            endOfStream = true;
+        }
+        return moreData;
+    }
+
+
+    /**
+     * After reading the request headers, we have to setup the request filters.
+     */
+    protected void prepareRequest() {
+
+        // Translate the HTTP method code to a String.
+        byte methodCode = requestHeaderMessage.getByte();
+        if (methodCode != Constants.SC_M_JK_STORED) {
+            String methodName = Constants.getMethodForCode(methodCode - 1);
+            request.method().setString(methodName);
+        }
+
+        requestHeaderMessage.getBytes(request.protocol());
+        requestHeaderMessage.getBytes(request.requestURI());
+
+        requestHeaderMessage.getBytes(request.remoteAddr());
+        requestHeaderMessage.getBytes(request.remoteHost());
+        requestHeaderMessage.getBytes(request.localName());
+        request.setLocalPort(requestHeaderMessage.getInt());
+
+        boolean isSSL = requestHeaderMessage.getByte() != 0;
+        if (isSSL) {
+            request.scheme().setString("https");
+        }
+
+        // Decode headers
+        MimeHeaders headers = request.getMimeHeaders();
+
+        // Set this every time in case limit has been changed via JMX
+        headers.setLimit(endpoint.getMaxHeaderCount());
+
+        boolean contentLengthSet = false;
+        int hCount = requestHeaderMessage.getInt();
+        for(int i = 0 ; i < hCount ; i++) {
+            String hName = null;
+
+            // Header names are encoded as either an integer code starting
+            // with 0xA0, or as a normal string (in which case the first
+            // two bytes are the length).
+            int isc = requestHeaderMessage.peekInt();
+            int hId = isc & 0xFF;
+
+            MessageBytes vMB = null;
+            isc &= 0xFF00;
+            if(0xA000 == isc) {
+                requestHeaderMessage.getInt(); // To advance the read position
+                hName = Constants.getHeaderForCode(hId - 1);
+                vMB = headers.addValue(hName);
+            } else {
+                // reset hId -- if the header currently being read
+                // happens to be 7 or 8 bytes long, the code below
+                // will think it's the content-type header or the
+                // content-length header - SC_REQ_CONTENT_TYPE=7,
+                // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
+                // behaviour.  see bug 5861 for more information.
+                hId = -1;
+                requestHeaderMessage.getBytes(tmpMB);
+                ByteChunk bc = tmpMB.getByteChunk();
+                vMB = headers.addValue(bc.getBuffer(),
+                        bc.getStart(), bc.getLength());
+            }
+
+            requestHeaderMessage.getBytes(vMB);
+
+            if (hId == Constants.SC_REQ_CONTENT_LENGTH ||
+                    (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
+                long cl = vMB.getLong();
+                if (contentLengthSet) {
+                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
+                    setErrorState(ErrorState.CLOSE_CLEAN, null);
+                } else {
+                    contentLengthSet = true;
+                    // Set the content-length header for the request
+                    request.setContentLength(cl);
+                }
+            } else if (hId == Constants.SC_REQ_CONTENT_TYPE ||
+                    (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
+                // just read the content-type header, so set it
+                ByteChunk bchunk = vMB.getByteChunk();
+                request.contentType().setBytes(bchunk.getBytes(),
+                        bchunk.getOffset(),
+                        bchunk.getLength());
+            }
+        }
+
+        // Decode extra attributes
+        boolean secret = false;
+        byte attributeCode;
+        while ((attributeCode = requestHeaderMessage.getByte())
+                != Constants.SC_A_ARE_DONE) {
+
+            switch (attributeCode) {
+
+            case Constants.SC_A_REQ_ATTRIBUTE :
+                requestHeaderMessage.getBytes(tmpMB);
+                String n = tmpMB.toString();
+                requestHeaderMessage.getBytes(tmpMB);
+                String v = tmpMB.toString();
+                /*
+                 * AJP13 misses to forward the local IP address and the
+                 * remote port. Allow the AJP connector to add this info via
+                 * private request attributes.
+                 * We will accept the forwarded data and remove it from the
+                 * public list of request attributes.
+                 */
+                if(n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
+                    request.localAddr().setString(v);
+                } else if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
+                    try {
+                        request.setRemotePort(Integer.parseInt(v));
+                    } catch (NumberFormatException nfe) {
+                        // Ignore invalid value
+                    }
+                } else {
+                    request.setAttribute(n, v );
+                }
+                break;
+
+            case Constants.SC_A_CONTEXT :
+                requestHeaderMessage.getBytes(tmpMB);
+                // nothing
+                break;
+
+            case Constants.SC_A_SERVLET_PATH :
+                requestHeaderMessage.getBytes(tmpMB);
+                // nothing
+                break;
+
+            case Constants.SC_A_REMOTE_USER :
+                if (tomcatAuthentication) {
+                    // ignore server
+                    requestHeaderMessage.getBytes(tmpMB);
+                } else {
+                    requestHeaderMessage.getBytes(request.getRemoteUser());
+                }
+                break;
+
+            case Constants.SC_A_AUTH_TYPE :
+                if (tomcatAuthentication) {
+                    // ignore server
+                    requestHeaderMessage.getBytes(tmpMB);
+                } else {
+                    requestHeaderMessage.getBytes(request.getAuthType());
+                }
+                break;
+
+            case Constants.SC_A_QUERY_STRING :
+                requestHeaderMessage.getBytes(request.queryString());
+                break;
+
+            case Constants.SC_A_JVM_ROUTE :
+                requestHeaderMessage.getBytes(request.instanceId());
+                break;
+
+            case Constants.SC_A_SSL_CERT :
+                // SSL certificate extraction is lazy, moved to JkCoyoteHandler
+                requestHeaderMessage.getBytes(certificates);
+                break;
+
+            case Constants.SC_A_SSL_CIPHER :
+                requestHeaderMessage.getBytes(tmpMB);
+                request.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
+                        tmpMB.toString());
+                break;
+
+            case Constants.SC_A_SSL_SESSION :
+                requestHeaderMessage.getBytes(tmpMB);
+                request.setAttribute(SSLSupport.SESSION_ID_KEY,
+                        tmpMB.toString());
+                break;
+
+            case Constants.SC_A_SSL_KEY_SIZE :
+                request.setAttribute(SSLSupport.KEY_SIZE_KEY,
+                        Integer.valueOf(requestHeaderMessage.getInt()));
+                break;
+
+            case Constants.SC_A_STORED_METHOD:
+                requestHeaderMessage.getBytes(request.method());
+                break;
+
+            case Constants.SC_A_SECRET:
+                requestHeaderMessage.getBytes(tmpMB);
+                if (requiredSecret != null) {
+                    secret = true;
+                    if (!tmpMB.equals(requiredSecret)) {
+                        response.setStatus(403);
+                        setErrorState(ErrorState.CLOSE_CLEAN, null);
+                    }
+                }
+                break;
+
+            default:
+                // Ignore unknown attribute for backward compatibility
+                break;
+
+            }
+
+        }
+
+        // Check if secret was submitted if required
+        if ((requiredSecret != null) && !secret) {
+            response.setStatus(403);
+            setErrorState(ErrorState.CLOSE_CLEAN, null);
+        }
+
+        // Check for a full URI (including protocol://host:port/)
+        ByteChunk uriBC = request.requestURI().getByteChunk();
+        if (uriBC.startsWithIgnoreCase("http", 0)) {
+
+            int pos = uriBC.indexOf("://", 0, 3, 4);
+            int uriBCStart = uriBC.getStart();
+            int slashPos = -1;
+            if (pos != -1) {
+                byte[] uriB = uriBC.getBytes();
+                slashPos = uriBC.indexOf('/', pos + 3);
+                if (slashPos == -1) {
+                    slashPos = uriBC.getLength();
+                    // Set URI as "/"
+                    request.requestURI().setBytes
+                    (uriB, uriBCStart + pos + 1, 1);
+                } else {
+                    request.requestURI().setBytes
+                    (uriB, uriBCStart + slashPos,
+                            uriBC.getLength() - slashPos);
+                }
+                MessageBytes hostMB = headers.setValue("host");
+                hostMB.setBytes(uriB, uriBCStart + pos + 3,
+                        slashPos - pos - 3);
+            }
+
+        }
+
+        MessageBytes valueMB = request.getMimeHeaders().getValue("host");
+        parseHost(valueMB);
+
+        if (getErrorState().isError()) {
+            getAdapter().log(request, response, 0);
+        }
+    }
+
+
+    /**
+     * Parse host.
+     */
+    protected void parseHost(MessageBytes valueMB) {
+
+        if (valueMB == null || valueMB.isNull()) {
+            // HTTP/1.0
+            request.setServerPort(request.getLocalPort());
+            try {
+                request.serverName().duplicate(request.localName());
+            } catch (IOException e) {
+                response.setStatus(400);
+                setErrorState(ErrorState.CLOSE_CLEAN, e);
+            }
+            return;
+        }
+
+        ByteChunk valueBC = valueMB.getByteChunk();
+        byte[] valueB = valueBC.getBytes();
+        int valueL = valueBC.getLength();
+        int valueS = valueBC.getStart();
+        int colonPos = -1;
+        if (hostNameC.length < valueL) {
+            hostNameC = new char[valueL];
+        }
+
+        boolean ipv6 = (valueB[valueS] == '[');
+        boolean bracketClosed = false;
+        for (int i = 0; i < valueL; i++) {
+            char b = (char) valueB[i + valueS];
+            hostNameC[i] = b;
+            if (b == ']') {
+                bracketClosed = true;
+            } else if (b == ':') {
+                if (!ipv6 || bracketClosed) {
+                    colonPos = i;
+                    break;
+                }
+            }
+        }
+
+        if (colonPos < 0) {
+            if (request.scheme().equalsIgnoreCase("https")) {
+                // 443 - Default HTTPS port
+                request.setServerPort(443);
+            } else {
+                // 80 - Default HTTTP port
+                request.setServerPort(80);
+            }
+            request.serverName().setChars(hostNameC, 0, valueL);
+        } else {
+
+            request.serverName().setChars(hostNameC, 0, colonPos);
+
+            int port = 0;
+            int mult = 1;
+            for (int i = valueL - 1; i > colonPos; i--) {
+                int charValue = HexUtils.getDec(valueB[i + valueS]);
+                if (charValue == -1) {
+                    // Invalid character
+                    // 400 - Bad request
+                    response.setStatus(400);
+                    setErrorState(ErrorState.CLOSE_CLEAN, null);
+                    break;
+                }
+                port = port + (charValue * mult);
+                mult = 10 * mult;
+            }
+            request.setServerPort(port);
+        }
+    }
+
+
+    /**
+     * When committing the response, we have to validate the set of headers, as
+     * well as setup the response filters.
+     */
+    protected void prepareResponse() throws IOException {
+
+        response.setCommitted(true);
+
+        tmpMB.recycle();
+        responseMsgPos = -1;
+        responseMessage.reset();
+        responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
+
+        // Responses with certain status codes are not permitted to include a
+        // response body.
+        int statusCode = response.getStatus();
+        if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
+                statusCode == 304) {
+            // No entity body
+            swallowResponse = true;
+        }
+
+        // Responses to HEAD requests are not permitted to include a response
+        // body.
+        MessageBytes methodMB = request.method();
+        if (methodMB.equals("HEAD")) {
+            // No entity body
+            swallowResponse = true;
+        }
+
+        // HTTP header contents
+        responseMessage.appendInt(statusCode);
+        String message = null;
+        if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
+                HttpMessages.isSafeInHttpHeader(response.getMessage())) {
+            message = response.getMessage();
+        }
+        if (message == null){
+            message = HttpMessages.getInstance(
+                    response.getLocale()).getMessage(response.getStatus());
+        }
+        if (message == null) {
+            // mod_jk + httpd 2.x fails with a null status message - bug 45026
+            message = Integer.toString(response.getStatus());
+        }
+        tmpMB.setString(message);
+        responseMessage.appendBytes(tmpMB);
+
+        // Special headers
+        MimeHeaders headers = response.getMimeHeaders();
+        String contentType = response.getContentType();
+        if (contentType != null) {
+            headers.setValue("Content-Type").setString(contentType);
+        }
+        String contentLanguage = response.getContentLanguage();
+        if (contentLanguage != null) {
+            headers.setValue("Content-Language").setString(contentLanguage);
+        }
+        long contentLength = response.getContentLengthLong();
+        if (contentLength >= 0) {
+            headers.setValue("Content-Length").setLong(contentLength);
+        }
+
+        // Other headers
+        int numHeaders = headers.size();
+        responseMessage.appendInt(numHeaders);
+        for (int i = 0; i < numHeaders; i++) {
+            MessageBytes hN = headers.getName(i);
+            int hC = Constants.getResponseAjpIndex(hN.toString());
+            if (hC > 0) {
+                responseMessage.appendInt(hC);
+            }
+            else {
+                responseMessage.appendBytes(hN);
+            }
+            MessageBytes hV=headers.getValue(i);
+            responseMessage.appendBytes(hV);
+        }
+
+        // Write to buffer
+        responseMessage.end();
+        output(responseMessage.getBuffer(), 0, responseMessage.getLen(), true);
+    }
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    protected void flush(boolean explicit) throws IOException {
+        // Calling code should ensure that there is no data in the buffers for
+        // non-blocking writes.
+        // TODO Validate the assertion above
+        if (explicit && !finished) {
+            // Send the flush message
+            output(flushMessageArray, 0, flushMessageArray.length, true);
+        }
+    }
+
+
+    /**
+     * Finish AJP response.
+     */
+    protected void finish() throws IOException {
+
+        if (!response.isCommitted()) {
+            // Validate and write response headers
+            try {
+                prepareResponse();
+            } catch (IOException e) {
+                setErrorState(ErrorState.CLOSE_NOW, e);
+                return;
+            }
+        }
+
+        if (finished)
+            return;
+
+        finished = true;
+
+        // Swallow the unread body packet if present
+        if (waitingForBodyMessage || first && request.getContentLengthLong() > 0) {
+            refillReadBuffer(true);
+        }
+
+        // Add the end message
+        if (getErrorState().isError()) {
+            output(endAndCloseMessageArray, 0, endAndCloseMessageArray.length, true);
+        } else {
+            output(endMessageArray, 0, endMessageArray.length, true);
+        }
+    }
+
+
+    private boolean available() {
+        if (endOfStream) {
+            return false;
+        }
+        if (empty) {
+            try {
+                refillReadBuffer(false);
+            } catch (IOException timeout) {
+                // Not ideal. This will indicate that data is available
+                // which should trigger a read which in turn will trigger
+                // another IOException and that one can be thrown.
+                return true;
+            }
+        }
+        return !empty;
+    }
+
+
+    private void writeData(ByteChunk chunk) throws IOException {
+        // Prevent timeout
+        socketWrapper.access();
+
+        boolean blocking = (response.getWriteListener() == null);
+        if (!blocking) {
+            flushBufferedData();
+        }
+
+        int len = chunk.getLength();
+        int off = 0;
+
+        // Write this chunk
+        while (responseMsgPos == -1 && len > 0) {
+            int thisTime = len;
+            if (thisTime > outputMaxChunkSize) {
+                thisTime = outputMaxChunkSize;
+            }
+            responseMessage.reset();
+            responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+            responseMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
+            responseMessage.end();
+            writeResponseMessage(blocking);
+
+            len -= thisTime;
+            off += thisTime;
+        }
+
+        bytesWritten += off;
+
+        if (len > 0) {
+            // Add this chunk to the buffer
+            addToBuffers(chunk.getBuffer(), off, len);
+        }
+    }
+
+
+    private void addToBuffers(byte[] buf, int offset, int length) {
+        ByteBufferHolder holder = bufferedWrites.peekLast();
+        if (holder == null || holder.isFlipped() || holder.getBuf().remaining() < length) {
+            ByteBuffer buffer = ByteBuffer.allocate(Math.max(bufferedWriteSize,length));
+            holder = new ByteBufferHolder(buffer, false);
+            bufferedWrites.add(holder);
+        }
+        holder.getBuf().put(buf, offset, length);
+    }
+
+
+    private boolean hasDataToWrite() {
+        return responseMsgPos != -1 || bufferedWrites.size() > 0;
+    }
+
+
+    private void flushBufferedData() throws IOException {
+
+        if (responseMsgPos > -1) {
+            // Must be using non-blocking IO
+            // Partially written response message. Try and complete it.
+            writeResponseMessage(false);
+        }
+
+        while (responseMsgPos == -1 && bufferedWrites.size() > 0) {
+            // Try and write any remaining buffer data
+            Iterator<ByteBufferHolder> holders = bufferedWrites.iterator();
+            ByteBufferHolder holder = holders.next();
+            holder.flip();
+            ByteBuffer buffer = holder.getBuf();
+            int initialBufferSize = buffer.remaining();
+            while (responseMsgPos == -1 && buffer.remaining() > 0) {
+                transferToResponseMsg(buffer);
+                writeResponseMessage(false);
+            }
+            bytesWritten += (initialBufferSize - buffer.remaining());
+            if (buffer.remaining() == 0) {
+                holders.remove();
+            }
+        }
+    }
+
+
+    private void transferToResponseMsg(ByteBuffer buffer) {
+
+        int thisTime = buffer.remaining();
+        if (thisTime > outputMaxChunkSize) {
+            thisTime = outputMaxChunkSize;
+        }
+
+        responseMessage.reset();
+        responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
+        buffer.get(responseMessage.getBuffer(), responseMessage.pos, thisTime);
+        responseMessage.end();
+    }
+
+
+    private void writeResponseMessage(boolean block) throws IOException {
+        int len = responseMessage.getLen();
+        int written = 1;
+        if (responseMsgPos == -1) {
+            // New message. Advance the write position to the beginning
+            responseMsgPos = 0;
+        }
+
+        while (written > 0 && responseMsgPos < len) {
+            written = output(
+                    responseMessage.getBuffer(), responseMsgPos, len - responseMsgPos, block);
+            responseMsgPos += written;
+        }
+
+        // Message fully written, reset the position for a new message.
+        if (responseMsgPos == len) {
+            responseMsgPos = -1;
+        }
+    }
+
+    // ------------------------------------- InputStreamInputBuffer Inner Class
+
+
+    /**
+     * This class is an input buffer which will read its data from an input
+     * stream.
+     */
+    protected class SocketInputBuffer implements InputBuffer {
+
+        /**
+         * Read bytes into the specified chunk.
+         */
+        @Override
+        public int doRead(ByteChunk chunk, Request req) throws IOException {
+
+            if (endOfStream) {
+                return -1;
+            }
+            if (empty) {
+                if (!refillReadBuffer(true)) {
+                    return -1;
+                }
+            }
+            ByteChunk bc = bodyBytes.getByteChunk();
+            chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength());
+            empty = true;
+            return chunk.getLength();
+        }
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer implements OutputBuffer {
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) throws IOException {
+
+            if (!response.isCommitted()) {
+                // Validate and write response headers
+                try {
+                    prepareResponse();
+                } catch (IOException e) {
+                    setErrorState(ErrorState.CLOSE_NOW, e);
+                }
+            }
+
+            if (!swallowResponse) {
+                writeData(chunk);
+            }
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return bytesWritten;
+        }
+    }
+}
diff --git a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
index 09c7345..0612cb2 100644
--- a/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
+++ b/java/org/apache/coyote/ajp/AbstractAjpProtocol.java
@@ -22,32 +22,16 @@
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
-/**
- * The is the base implementation for the AJP protocol handlers. Implementations
- * typically extend this base class rather than implement {@link
- * org.apache.coyote.ProtocolHandler}. All of the implementations that ship with
- * Tomcat are implemented this way.
- *
- * @param <S> The type of socket used by the implementation
- */
 public abstract class AbstractAjpProtocol<S> extends AbstractProtocol<S> {
 
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(AbstractAjpProtocol.class);
-
-
-    public AbstractAjpProtocol(AbstractEndpoint<S> endpoint) {
-        super(endpoint);
-        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
-        // AJP does not use Send File
-        getEndpoint().setUseSendfile(false);
-    }
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
 
 
     @Override
@@ -56,16 +40,6 @@
     }
 
 
-    /**
-     * {@inheritDoc}
-     *
-     * Overridden to make getter accessible to other classes in this package.
-     */
-    @Override
-    protected AbstractEndpoint<S> getEndpoint() {
-        return super.getEndpoint();
-    }
-
 
     // ------------------------------------------------- AJP specific properties
     // ------------------------------------------ managed in the ProtocolHandler
@@ -74,7 +48,7 @@
      * Should authentication be done in the native webserver layer,
      * or in the Servlet container ?
      */
-    private boolean tomcatAuthentication = true;
+    protected boolean tomcatAuthentication = true;
     public boolean getTomcatAuthentication() { return tomcatAuthentication; }
     public void setTomcatAuthentication(boolean tomcatAuthentication) {
         this.tomcatAuthentication = tomcatAuthentication;
@@ -84,7 +58,7 @@
     /**
      * Required secret.
      */
-    private String requiredSecret = null;
+    protected String requiredSecret = null;
     public void setRequiredSecret(String requiredSecret) {
         this.requiredSecret = requiredSecret;
     }
@@ -93,7 +67,7 @@
     /**
      * AJP packet size.
      */
-    private int packetSize = Constants.MAX_PACKET_SIZE;
+    protected int packetSize = Constants.MAX_PACKET_SIZE;
     public int getPacketSize() { return packetSize; }
     public void setPacketSize(int packetSize) {
         if(packetSize < Constants.MAX_PACKET_SIZE) {
@@ -103,7 +77,7 @@
         }
     }
 
-    protected void configureProcessor(AjpProcessor<S> processor) {
+    protected void configureProcessor(AbstractAjpProcessor<S> processor) {
         processor.setAdapter(getAdapter());
         processor.setTomcatAuthentication(getTomcatAuthentication());
         processor.setRequiredSecret(requiredSecret);
@@ -111,45 +85,24 @@
         processor.setClientCertProvider(getClientCertProvider());
     }
 
-    protected abstract static class AbstractAjpConnectionHandler<S>
-            extends AbstractConnectionHandler<S,AjpProcessor<S>> {
-
-        private final AbstractAjpProtocol<S> proto;
-
-        public AbstractAjpConnectionHandler(AbstractAjpProtocol<S> proto) {
-            this.proto = proto;
-        }
+    protected abstract static class AbstractAjpConnectionHandler<S,P extends AbstractAjpProcessor<S>>
+            extends AbstractConnectionHandler<S, P> {
 
         @Override
-        protected AbstractAjpProtocol<S> getProtocol() {
-            return proto;
-        }
-
-
-        @Override
-        protected AjpProcessor<S> createProcessor() {
-            AjpProcessor<S> processor =
-                    new AjpProcessor<>(proto.getPacketSize(), proto.getEndpoint());
-            proto.configureProcessor(processor);
-            register(processor);
-            return processor;
-        }
-
-        @Override
-        protected void initSsl(SocketWrapperBase<S> socket, Processor<S> processor) {
+        protected void initSsl(SocketWrapper<S> socket, Processor<S> processor) {
             // NOOP for AJP
         }
 
         @Override
-        protected void longPoll(SocketWrapperBase<S> socket,
+        protected void longPoll(SocketWrapper<S> socket,
                 Processor<S> processor) {
             // Same requirements for all AJP connectors
             socket.setAsync(true);
         }
 
         @Override
-        protected AjpProcessor<S> createUpgradeProcessor(SocketWrapperBase<S> socket,
-                ByteBuffer leftoverInput, HttpUpgradeHandler httpUpgradeHandler) {
+        protected P createUpgradeProcessor(SocketWrapper<S> socket, ByteBuffer leftoverInput,
+                HttpUpgradeHandler httpUpgradeHandler) {
             // TODO should fail - throw IOE
             return null;
         }
diff --git a/java/org/apache/coyote/ajp/AjpAprProcessor.java b/java/org/apache/coyote/ajp/AjpAprProcessor.java
new file mode 100644
index 0000000..ac7640c
--- /dev/null
+++ b/java/org/apache/coyote/ajp/AjpAprProcessor.java
@@ -0,0 +1,289 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.ajp;
+
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+/**
+ * Processes AJP requests.
+ *
+ * @author Remy Maucherat
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
+ */
+public class AjpAprProcessor extends AbstractAjpProcessor<Long> {
+
+    private static final Log log = LogFactory.getLog(AjpAprProcessor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+
+    public AjpAprProcessor(int packetSize, AprEndpoint endpoint) {
+
+        super(packetSize, endpoint);
+
+        response.setOutputBuffer(new SocketOutputBuffer());
+
+        // Allocate input and output buffers
+        inputBuffer = ByteBuffer.allocateDirect(packetSize * 2);
+        inputBuffer.limit(0);
+        outputBuffer = ByteBuffer.allocateDirect(packetSize * 2);
+    }
+
+
+    /**
+     * Direct buffer used for input.
+     */
+    protected final ByteBuffer inputBuffer;
+
+
+    /**
+     * Direct buffer used for output.
+     */
+    protected final ByteBuffer outputBuffer;
+
+
+    @Override
+    protected void registerForEvent(boolean read, boolean write) {
+        ((AprEndpoint) endpoint).getPoller().add(
+                socketWrapper.getSocket().longValue(), -1, read, write);
+    }
+
+    @Override
+    protected void resetTimeouts() {
+        // NO-OP. The AJP APR/native connector only uses the timeout value on
+        //        time SocketWrapper for async timeouts.
+    }
+
+
+    @Override
+    protected void setupSocket(SocketWrapper<Long> socketWrapper) {
+        long socketRef = socketWrapper.getSocket().longValue();
+        Socket.setrbb(socketRef, inputBuffer);
+        Socket.setsbb(socketRef, outputBuffer);
+    }
+
+
+    @Override
+    protected void setTimeout(SocketWrapper<Long> socketWrapper,
+            int timeout) throws IOException {
+        Socket.timeoutSet(
+                socketWrapper.getSocket().longValue(), timeout * 1000);
+    }
+
+
+    @Override
+    protected int output(byte[] src, int offset, int length, boolean block)
+            throws IOException {
+
+        if (length == 0) {
+            return 0;
+        }
+
+        outputBuffer.put(src, offset, length);
+
+        int result = -1;
+
+        if (socketWrapper.getSocket().longValue() != 0) {
+            result = writeSocket(0, outputBuffer.position(), block);
+            if (Status.APR_STATUS_IS_EAGAIN(-result)) {
+                result = 0;
+            }
+            if (result < 0) {
+                // There are no re-tries so clear the buffer to prevent a
+                // possible overflow if the buffer is used again. BZ53119.
+                outputBuffer.clear();
+                throw new IOException(sm.getString("ajpprocessor.failedsend"));
+            }
+        }
+        outputBuffer.clear();
+
+        return result;
+    }
+
+
+    private int writeSocket(int pos, int len, boolean block) {
+
+        Lock readLock = socketWrapper.getBlockingStatusReadLock();
+        WriteLock writeLock = socketWrapper.getBlockingStatusWriteLock();
+        long socket = socketWrapper.getSocket().longValue();
+
+        boolean writeDone = false;
+        int result = 0;
+        readLock.lock();
+        try {
+            if (socketWrapper.getBlockingStatus() == block) {
+                result = Socket.sendbb(socket, pos, len);
+                writeDone = true;
+            }
+        } finally {
+            readLock.unlock();
+        }
+
+        if (!writeDone) {
+            writeLock.lock();
+            try {
+                socketWrapper.setBlockingStatus(block);
+                // Set the current settings for this socket
+                Socket.optSet(socket, Socket.APR_SO_NONBLOCK, (block ? 0 : 1));
+                // Downgrade the lock
+                readLock.lock();
+                try {
+                    writeLock.unlock();
+                    result = Socket.sendbb(socket, pos, len);
+                } finally {
+                    readLock.unlock();
+                }
+            } finally {
+                // Should have been released above but may not have been on some
+                // exception paths
+                if (writeLock.isHeldByCurrentThread()) {
+                    writeLock.unlock();
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    @Override
+    protected boolean read(byte[] buf, int pos, int n, boolean block)
+            throws IOException {
+
+        boolean nextReadBlocks = block;
+
+        if (!block && inputBuffer.remaining() > 0) {
+            nextReadBlocks = true;
+        }
+
+        if (inputBuffer.capacity() - inputBuffer.limit() <=
+                n - inputBuffer.remaining()) {
+            inputBuffer.compact();
+            inputBuffer.limit(inputBuffer.position());
+            inputBuffer.position(0);
+        }
+        int nRead;
+        while (inputBuffer.remaining() < n) {
+            nRead = readSocket(inputBuffer.limit(),
+                    inputBuffer.capacity() - inputBuffer.limit(),
+                    nextReadBlocks);
+            if (nRead == 0) {
+                // Must be a non-blocking read
+                return false;
+            } else if (-nRead == Status.EAGAIN) {
+                return false;
+            } else if ((-nRead) == Status.ETIMEDOUT || (-nRead) == Status.TIMEUP) {
+                if (block) {
+                    throw new SocketTimeoutException(
+                            sm.getString("ajpprocessor.readtimeout"));
+                } else {
+                    // Attempting to read from the socket when the poller
+                    // has not signalled that there is data to read appears
+                    // to behave like a blocking read with a short timeout
+                    // on OSX rather than like a non-blocking read. If no
+                    // data is read, treat the resulting timeout like a
+                    // non-blocking read that returned no data.
+                    return false;
+                }
+            } else if (nRead > 0) {
+                inputBuffer.limit(inputBuffer.limit() + nRead);
+                nextReadBlocks = true;
+            } else {
+                throw new IOException(sm.getString("ajpprocessor.failedread"));
+            }
+        }
+
+        inputBuffer.get(buf, pos, n);
+        return true;
+    }
+
+
+    private int readSocket(int pos, int len, boolean block) {
+
+        Lock readLock = socketWrapper.getBlockingStatusReadLock();
+        WriteLock writeLock = socketWrapper.getBlockingStatusWriteLock();
+        long socket = socketWrapper.getSocket().longValue();
+
+        boolean readDone = false;
+        int result = 0;
+        readLock.lock();
+        try {
+            if (socketWrapper.getBlockingStatus() == block) {
+                result = Socket.recvbb(socket, pos, len);
+                readDone = true;
+            }
+        } finally {
+            readLock.unlock();
+        }
+
+        if (!readDone) {
+            writeLock.lock();
+            try {
+                socketWrapper.setBlockingStatus(block);
+                // Set the current settings for this socket
+                Socket.optSet(socket, Socket.APR_SO_NONBLOCK, (block ? 0 : 1));
+                // Downgrade the lock
+                readLock.lock();
+                try {
+                    writeLock.unlock();
+                    result = Socket.recvbb(socket, pos, len);
+                } finally {
+                    readLock.unlock();
+                }
+            } finally {
+                // Should have been released above but may not have been on some
+                // exception paths
+                if (writeLock.isHeldByCurrentThread()) {
+                    writeLock.unlock();
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    /**
+     * Recycle the processor.
+     */
+    @Override
+    public void recycle(boolean socketClosing) {
+        super.recycle(socketClosing);
+
+        inputBuffer.clear();
+        inputBuffer.limit(0);
+        outputBuffer.clear();
+
+    }
+}
diff --git a/java/org/apache/coyote/ajp/AjpAprProtocol.java b/java/org/apache/coyote/ajp/AjpAprProtocol.java
index 4223355..1a3ecb6 100644
--- a/java/org/apache/coyote/ajp/AjpAprProtocol.java
+++ b/java/org/apache/coyote/ajp/AjpAprProtocol.java
@@ -16,18 +16,27 @@
  */
 package org.apache.coyote.ajp;
 
+import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AprEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.AprEndpoint.Handler;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
- * This the APR/native based protocol handler implementation for AJP.
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
  */
 public class AjpAprProtocol extends AbstractAjpProtocol<Long> {
 
+
     private static final Log log = LogFactory.getLog(AjpAprProtocol.class);
 
     @Override
@@ -35,6 +44,12 @@
 
 
     @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    @Override
     public boolean isAprRequired() {
         // Override since this protocol implementation requires the APR/native
         // library
@@ -45,21 +60,35 @@
     // ------------------------------------------------------------ Constructor
 
     public AjpAprProtocol() {
-        super(new AprEndpoint());
-        AjpConnectionHandler cHandler = new AjpConnectionHandler(this);
-        setHandler(cHandler);
-        ((AprEndpoint) getEndpoint()).setHandler(cHandler);
+        endpoint = new AprEndpoint();
+        cHandler = new AjpConnectionHandler(this);
+        ((AprEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        // AJP does not use Send File
+        ((AprEndpoint) endpoint).setUseSendfile(false);
     }
 
 
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Connection handler for AJP.
+     */
+    private final AjpConnectionHandler cHandler;
+
+
     // --------------------------------------------------------- Public Methods
 
-    public int getPollTime() { return ((AprEndpoint)getEndpoint()).getPollTime(); }
-    public void setPollTime(int pollTime) { ((AprEndpoint)getEndpoint()).setPollTime(pollTime); }
+
+    public int getPollTime() { return ((AprEndpoint)endpoint).getPollTime(); }
+    public void setPollTime(int pollTime) { ((AprEndpoint)endpoint).setPollTime(pollTime); }
 
     // pollerSize is now a synonym for maxConnections
-    public void setPollerSize(int pollerSize) { getEndpoint().setMaxConnections(pollerSize); }
-    public int getPollerSize() { return getEndpoint().getMaxConnections(); }
+    public void setPollerSize(int pollerSize) { endpoint.setMaxConnections(pollerSize); }
+    public int getPollerSize() { return endpoint.getMaxConnections(); }
 
 
     // ----------------------------------------------------- JMX related methods
@@ -72,11 +101,20 @@
 
     // --------------------------------------  AjpConnectionHandler Inner Class
 
+
     protected static class AjpConnectionHandler
-            extends AbstractAjpConnectionHandler<Long> {
+            extends AbstractAjpConnectionHandler<Long,AjpAprProcessor>
+            implements Handler {
+
+        protected final AjpAprProtocol proto;
 
         public AjpConnectionHandler(AjpAprProtocol proto) {
-            super(proto);
+            this.proto = proto;
+        }
+
+        @Override
+        protected AbstractProtocol<Long> getProtocol() {
+            return proto;
         }
 
         @Override
@@ -89,16 +127,25 @@
          * required.
          */
         @Override
-        public void release(SocketWrapperBase<Long> socket,
+        public void release(SocketWrapper<Long> socket,
                 Processor<Long> processor, boolean isSocketClosing,
                 boolean addToPoller) {
             processor.recycle(isSocketClosing);
             recycledProcessors.push(processor);
             if (addToPoller) {
-                ((AprEndpoint)getProtocol().getEndpoint()).getPoller().add(
+                ((AprEndpoint)proto.endpoint).getPoller().add(
                         socket.getSocket().longValue(),
-                        getProtocol().getEndpoint().getKeepAliveTimeout(), true, false);
+                        proto.endpoint.getKeepAliveTimeout(), true, false);
             }
         }
+
+
+        @Override
+        protected AjpAprProcessor createProcessor() {
+            AjpAprProcessor processor = new AjpAprProcessor(proto.packetSize, (AprEndpoint)proto.endpoint);
+            proto.configureProcessor(processor);
+            register(processor);
+            return processor;
+        }
     }
 }
diff --git a/java/org/apache/coyote/ajp/AjpMessage.java b/java/org/apache/coyote/ajp/AjpMessage.java
index 4dae3e2..e4a5ee7 100644
--- a/java/org/apache/coyote/ajp/AjpMessage.java
+++ b/java/org/apache/coyote/ajp/AjpMessage.java
@@ -45,7 +45,8 @@
     /**
      * The string manager for this package.
      */
-    protected static final StringManager sm = StringManager.getManager(AjpMessage.class);
+    protected static final StringManager sm =
+        StringManager.getManager(Constants.Package);
 
 
     // ------------------------------------------------------------ Constructor
@@ -299,6 +300,16 @@
     }
 
 
+    public int getHeaderLength() {
+        return Constants.H_SIZE;
+    }
+
+
+    public int getPacketSize() {
+        return buf.length;
+    }
+
+
     public int processHeader(boolean toContainer) {
         pos = 0;
         int mark = getInt();
diff --git a/java/org/apache/coyote/ajp/AjpNio2Processor.java b/java/org/apache/coyote/ajp/AjpNio2Processor.java
new file mode 100644
index 0000000..45a0cb1
--- /dev/null
+++ b/java/org/apache/coyote/ajp/AjpNio2Processor.java
@@ -0,0 +1,262 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.ajp;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.Nio2Channel;
+import org.apache.tomcat.util.net.Nio2Endpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+/**
+ * Processes AJP requests using NIO2.
+ */
+public class AjpNio2Processor extends AbstractAjpProcessor<Nio2Channel> {
+
+    private static final Log log = LogFactory.getLog(AjpNio2Processor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+    /**
+     * The completion handler used for asynchronous write operations
+     */
+    protected CompletionHandler<Integer, SocketWrapper<Nio2Channel>> writeCompletionHandler;
+
+    /**
+     * Flipped flag for read buffer.
+     */
+    protected boolean flipped = false;
+
+    /**
+     * Write pending flag.
+     */
+    protected volatile boolean writePending = false;
+
+    public AjpNio2Processor(int packetSize, Nio2Endpoint endpoint0) {
+        super(packetSize, endpoint0);
+        response.setOutputBuffer(new SocketOutputBuffer());
+        this.writeCompletionHandler = new CompletionHandler<Integer, SocketWrapper<Nio2Channel>>() {
+            @Override
+            public void completed(Integer nBytes, SocketWrapper<Nio2Channel> attachment) {
+                boolean notify = false;
+                synchronized (writeCompletionHandler) {
+                    if (nBytes.intValue() < 0) {
+                        failed(new IOException(sm.getString("ajpprocessor.failedsend")), attachment);
+                        return;
+                    }
+                    writePending = false;
+                    if (!Nio2Endpoint.isInline()) {
+                        notify = true;
+                    }
+                }
+                if (notify) {
+                    endpoint.processSocket(attachment, SocketStatus.OPEN_WRITE, false);
+                }
+            }
+            @Override
+            public void failed(Throwable exc, SocketWrapper<Nio2Channel> attachment) {
+                attachment.setError(true);
+                writePending = false;
+                endpoint.processSocket(attachment, SocketStatus.DISCONNECT, true);
+            }
+        };
+    }
+
+    @Override
+    public void recycle(boolean socketClosing) {
+        super.recycle(socketClosing);
+        writePending = false;
+        flipped = false;
+    }
+
+    @Override
+    protected void registerForEvent(boolean read, boolean write) {
+        // Nothing to do here, the appropriate operations should
+        // already be pending
+    }
+
+    @Override
+    protected void resetTimeouts() {
+        // The NIO connector uses the timeout configured on the wrapper in the
+        // poller. Therefore, it needs to be reset once asycn processing has
+        // finished.
+        if (!getErrorState().isError() && socketWrapper != null &&
+                asyncStateMachine.isAsyncDispatching()) {
+            long soTimeout = endpoint.getSoTimeout();
+
+            //reset the timeout
+            if (keepAliveTimeout > 0) {
+                socketWrapper.setTimeout(keepAliveTimeout);
+            } else {
+                socketWrapper.setTimeout(soTimeout);
+            }
+        }
+
+    }
+
+
+    @Override
+    protected void setupSocket(SocketWrapper<Nio2Channel> socketWrapper)
+            throws IOException {
+        // NO-OP
+    }
+
+
+    @Override
+    protected void setTimeout(SocketWrapper<Nio2Channel> socketWrapper,
+            int timeout) throws IOException {
+        socketWrapper.setTimeout(timeout);
+    }
+
+
+    @Override
+    protected int output(byte[] src, int offset, int length, boolean block)
+            throws IOException {
+
+        if (socketWrapper == null || socketWrapper.getSocket() == null)
+            return -1;
+
+        ByteBuffer writeBuffer =
+                socketWrapper.getSocket().getBufHandler().getWriteBuffer();
+
+        int result = 0;
+        if (block) {
+            writeBuffer.clear();
+            writeBuffer.put(src, offset, length);
+            writeBuffer.flip();
+            try {
+                result = socketWrapper.getSocket().write(writeBuffer)
+                        .get(socketWrapper.getTimeout(), TimeUnit.MILLISECONDS).intValue();
+            } catch (InterruptedException | ExecutionException
+                    | TimeoutException e) {
+                throw new IOException(sm.getString("ajpprocessor.failedsend"), e);
+            }
+        } else {
+            synchronized (writeCompletionHandler) {
+                if (!writePending) {
+                    writeBuffer.clear();
+                    writeBuffer.put(src, offset, length);
+                    writeBuffer.flip();
+                    writePending = true;
+                    Nio2Endpoint.startInline();
+                    socketWrapper.getSocket().write(writeBuffer, socketWrapper.getTimeout(),
+                            TimeUnit.MILLISECONDS, socketWrapper, writeCompletionHandler);
+                    Nio2Endpoint.endInline();
+                    result = length;
+                }
+            }
+        }
+        return result;
+    }
+
+
+    @Override
+    protected boolean read(byte[] buf, int pos, int n, boolean blockFirstRead)
+        throws IOException {
+
+        int read = 0;
+        int res = 0;
+        boolean block = blockFirstRead;
+
+        while (read < n) {
+            res = readSocket(buf, read + pos, n - read, block);
+            if (res > 0) {
+                read += res;
+            } else if (res == 0 && !block) {
+                return false;
+            } else {
+                throw new IOException(sm.getString("ajpprocessor.failedread"));
+            }
+            block = true;
+        }
+        return true;
+    }
+
+
+    private int readSocket(byte[] buf, int pos, int n, boolean block)
+            throws IOException {
+        int nRead = 0;
+        ByteBuffer readBuffer =
+                socketWrapper.getSocket().getBufHandler().getReadBuffer();
+
+        if (block) {
+            if (!flipped) {
+                readBuffer.flip();
+                flipped = true;
+            }
+            if (readBuffer.remaining() > 0) {
+                nRead = Math.min(n, readBuffer.remaining());
+                readBuffer.get(buf, pos, nRead);
+                if (readBuffer.remaining() == 0) {
+                    readBuffer.clear();
+                    flipped = false;
+                }
+            } else {
+                readBuffer.clear();
+                flipped = false;
+                readBuffer.limit(n);
+                try {
+                    nRead = socketWrapper.getSocket().read(readBuffer)
+                            .get(socketWrapper.getTimeout(), TimeUnit.MILLISECONDS).intValue();
+                } catch (InterruptedException | ExecutionException
+                        | TimeoutException e) {
+                    throw new IOException(sm.getString("ajpprocessor.failedread"), e);
+                }
+                if (nRead > 0) {
+                    if (!flipped) {
+                        readBuffer.flip();
+                        flipped = true;
+                    }
+                    nRead = Math.min(n, readBuffer.remaining());
+                    readBuffer.get(buf, pos, nRead);
+                    if (readBuffer.remaining() == 0) {
+                        readBuffer.clear();
+                        flipped = false;
+                    }
+                }
+            }
+        } else {
+            if (!flipped) {
+                readBuffer.flip();
+                flipped = true;
+            }
+            if (readBuffer.remaining() > 0) {
+                nRead = Math.min(n, readBuffer.remaining());
+                readBuffer.get(buf, pos, nRead);
+                if (readBuffer.remaining() == 0) {
+                    readBuffer.clear();
+                    flipped = false;
+                }
+            } else {
+                readBuffer.clear();
+                flipped = false;
+                readBuffer.limit(n);
+            }
+        }
+        return nRead;
+    }
+}
diff --git a/java/org/apache/coyote/ajp/AjpNio2Protocol.java b/java/org/apache/coyote/ajp/AjpNio2Protocol.java
index 1cfa632..df72013 100644
--- a/java/org/apache/coyote/ajp/AjpNio2Protocol.java
+++ b/java/org/apache/coyote/ajp/AjpNio2Protocol.java
@@ -18,37 +18,63 @@
 
 import javax.net.ssl.SSLEngine;
 
+import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.Nio2Channel;
 import org.apache.tomcat.util.net.Nio2Endpoint;
 import org.apache.tomcat.util.net.Nio2Endpoint.Handler;
 import org.apache.tomcat.util.net.SSLImplementation;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
- * This the NIO2 based protocol handler implementation for AJP.
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
  */
 public class AjpNio2Protocol extends AbstractAjpProtocol<Nio2Channel> {
 
+
     private static final Log log = LogFactory.getLog(AjpNio2Protocol.class);
 
     @Override
     protected Log getLog() { return log; }
 
 
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
     // ------------------------------------------------------------ Constructor
 
+
     public AjpNio2Protocol() {
-        super(new Nio2Endpoint());
-        AjpConnectionHandler cHandler = new AjpConnectionHandler(this);
-        setHandler(cHandler);
-        ((Nio2Endpoint) getEndpoint()).setHandler(cHandler);
+        endpoint = new Nio2Endpoint();
+        cHandler = new AjpConnectionHandler(this);
+        ((Nio2Endpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        // AJP does not use Send File
+        ((Nio2Endpoint) endpoint).setUseSendfile(false);
     }
 
 
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Connection handler for AJP.
+     */
+    private final AjpConnectionHandler cHandler;
+
+
     // ----------------------------------------------------- JMX related methods
 
     @Override
@@ -59,12 +85,20 @@
 
     // --------------------------------------  AjpConnectionHandler Inner Class
 
+
     protected static class AjpConnectionHandler
-            extends AbstractAjpConnectionHandler<Nio2Channel>
+            extends AbstractAjpConnectionHandler<Nio2Channel, AjpNio2Processor>
             implements Handler {
 
+        protected final AjpNio2Protocol proto;
+
         public AjpConnectionHandler(AjpNio2Protocol proto) {
-            super(proto);
+            this.proto = proto;
+        }
+
+        @Override
+        protected AbstractProtocol<Nio2Channel> getProtocol() {
+            return proto;
         }
 
         @Override
@@ -83,7 +117,7 @@
          * close, errors etc.
          */
         @Override
-        public void release(SocketWrapperBase<Nio2Channel> socket) {
+        public void release(SocketWrapper<Nio2Channel> socket) {
             Processor<Nio2Channel> processor =
                     connections.remove(socket.getSocket());
             if (processor != null) {
@@ -97,16 +131,22 @@
          * required.
          */
         @Override
-        public void release(SocketWrapperBase<Nio2Channel> socket,
+        public void release(SocketWrapper<Nio2Channel> socket,
                 Processor<Nio2Channel> processor, boolean isSocketClosing,
                 boolean addToPoller) {
-            if (getLog().isDebugEnabled()) {
-                log.debug("Socket: [" + socket + "], Processor: [" + processor +
-                        "], isSocketClosing: [" + isSocketClosing +
-                        "], addToPoller: [" + addToPoller + "]");
-            }
             processor.recycle(isSocketClosing);
             recycledProcessors.push(processor);
+            if (addToPoller) {
+                ((Nio2Endpoint) proto.endpoint).awaitBytes(socket);
+            }
+        }
+
+        @Override
+        protected AjpNio2Processor createProcessor() {
+            AjpNio2Processor processor = new AjpNio2Processor(proto.packetSize, (Nio2Endpoint) proto.endpoint);
+            proto.configureProcessor(processor);
+            register(processor);
+            return processor;
         }
 
         @Override
@@ -116,7 +156,7 @@
         @Override
         public void closeAll() {
             for (Nio2Channel channel : connections.keySet()) {
-                ((Nio2Endpoint) getProtocol().getEndpoint()).closeSocket(channel.getSocket());
+                ((Nio2Endpoint) proto.endpoint).closeSocket(channel.getSocket(), SocketStatus.STOP);
             }
         }
     }
diff --git a/java/org/apache/coyote/ajp/AjpNioProcessor.java b/java/org/apache/coyote/ajp/AjpNioProcessor.java
new file mode 100644
index 0000000..38378c0
--- /dev/null
+++ b/java/org/apache/coyote/ajp/AjpNioProcessor.java
@@ -0,0 +1,213 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.ajp;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+/**
+ * Processes AJP requests using NIO.
+ */
+public class AjpNioProcessor extends AbstractAjpProcessor<NioChannel> {
+
+    private static final Log log = LogFactory.getLog(AjpNioProcessor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+
+    public AjpNioProcessor(int packetSize, NioEndpoint endpoint) {
+
+        super(packetSize, endpoint);
+
+        response.setOutputBuffer(new SocketOutputBuffer());
+
+        pool = endpoint.getSelectorPool();
+    }
+
+
+    /**
+     * Selector pool for the associated endpoint.
+     */
+    protected final NioSelectorPool pool;
+
+
+    @Override
+    protected void registerForEvent(boolean read, boolean write) {
+        final NioChannel socket = socketWrapper.getSocket();
+        final NioEndpoint.KeyAttachment attach =
+                (NioEndpoint.KeyAttachment) socket.getAttachment();
+        if (attach == null) {
+            return;
+        }
+        SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
+        if (read) {
+            attach.interestOps(attach.interestOps() | SelectionKey.OP_READ);
+            key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+        }
+        if (write) {
+            attach.interestOps(attach.interestOps() | SelectionKey.OP_WRITE);
+            key.interestOps(key.interestOps() | SelectionKey.OP_READ);
+        }
+    }
+
+
+    @Override
+    protected void resetTimeouts() {
+        // The NIO connector uses the timeout configured on the wrapper in the
+        // poller. Therefore, it needs to be reset once asycn processing has
+        // finished.
+        final NioEndpoint.KeyAttachment attach =
+                (NioEndpoint.KeyAttachment)socketWrapper.getSocket().getAttachment();
+        if (!getErrorState().isError() && attach != null &&
+                asyncStateMachine.isAsyncDispatching()) {
+            long soTimeout = endpoint.getSoTimeout();
+
+            //reset the timeout
+            if (keepAliveTimeout > 0) {
+                attach.setTimeout(keepAliveTimeout);
+            } else {
+                attach.setTimeout(soTimeout);
+            }
+        }
+
+    }
+
+
+    @Override
+    protected void setupSocket(SocketWrapper<NioChannel> socketWrapper)
+            throws IOException {
+        // NO-OP
+    }
+
+
+    @Override
+    protected void setTimeout(SocketWrapper<NioChannel> socketWrapper,
+            int timeout) throws IOException {
+        socketWrapper.setTimeout(timeout);
+    }
+
+
+    @Override
+    protected int output(byte[] src, int offset, int length, boolean block)
+            throws IOException {
+
+        NioEndpoint.KeyAttachment att =
+                (NioEndpoint.KeyAttachment) socketWrapper.getSocket().getAttachment();
+        if ( att == null ) throw new IOException("Key must be cancelled");
+
+        ByteBuffer writeBuffer =
+                socketWrapper.getSocket().getBufHandler().getWriteBuffer();
+
+        writeBuffer.put(src, offset, length);
+
+        writeBuffer.flip();
+
+        long writeTimeout = att.getWriteTimeout();
+        Selector selector = null;
+        try {
+            selector = pool.get();
+        } catch (IOException x) {
+            //ignore
+        }
+        try {
+            return pool.write(writeBuffer, socketWrapper.getSocket(), selector,
+                    writeTimeout, block);
+        } finally {
+            writeBuffer.clear();
+            if (selector != null) {
+                pool.put(selector);
+            }
+        }
+    }
+
+
+    @Override
+    protected boolean read(byte[] buf, int pos, int n, boolean blockFirstRead)
+        throws IOException {
+
+        int read = 0;
+        int res = 0;
+        boolean block = blockFirstRead;
+
+        while (read < n) {
+            res = readSocket(buf, read + pos, n - read, block);
+            if (res > 0) {
+                read += res;
+            } else if (res == 0 && !block) {
+                return false;
+            } else {
+                throw new IOException(sm.getString("ajpprocessor.failedread"));
+            }
+            block = true;
+        }
+        return true;
+    }
+
+
+    private int readSocket(byte[] buf, int pos, int n, boolean block)
+            throws IOException {
+        int nRead = 0;
+        ByteBuffer readBuffer =
+                socketWrapper.getSocket().getBufHandler().getReadBuffer();
+        readBuffer.clear();
+        readBuffer.limit(n);
+        if ( block ) {
+            Selector selector = null;
+            try {
+                selector = pool.get();
+            } catch ( IOException x ) {
+                // Ignore
+            }
+            try {
+                NioEndpoint.KeyAttachment att =
+                        (NioEndpoint.KeyAttachment) socketWrapper.getSocket().getAttachment();
+                if ( att == null ) throw new IOException("Key must be cancelled.");
+                nRead = pool.read(readBuffer, socketWrapper.getSocket(),
+                        selector, att.getTimeout());
+            } catch ( EOFException eof ) {
+                nRead = -1;
+            } finally {
+                if ( selector != null ) pool.put(selector);
+            }
+        } else {
+            nRead = socketWrapper.getSocket().read(readBuffer);
+        }
+        if (nRead > 0) {
+            readBuffer.flip();
+            readBuffer.limit(nRead);
+            readBuffer.get(buf, pos, nRead);
+            return nRead;
+        } else if (nRead == -1) {
+            //return false;
+            throw new EOFException(sm.getString("iib.eof.error"));
+        } else {
+            return 0;
+        }
+    }
+}
diff --git a/java/org/apache/coyote/ajp/AjpNioProtocol.java b/java/org/apache/coyote/ajp/AjpNioProtocol.java
index bc08613..35a239a 100644
--- a/java/org/apache/coyote/ajp/AjpNioProtocol.java
+++ b/java/org/apache/coyote/ajp/AjpNioProtocol.java
@@ -21,36 +21,62 @@
 
 import javax.net.ssl.SSLEngine;
 
+import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.NioChannel;
 import org.apache.tomcat.util.net.NioEndpoint;
 import org.apache.tomcat.util.net.NioEndpoint.Handler;
 import org.apache.tomcat.util.net.SSLImplementation;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
+
 
 /**
- * This the NIO based protocol handler implementation for AJP.
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
  */
 public class AjpNioProtocol extends AbstractAjpProtocol<NioChannel> {
 
+
     private static final Log log = LogFactory.getLog(AjpNioProtocol.class);
 
     @Override
     protected Log getLog() { return log; }
 
 
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
     // ------------------------------------------------------------ Constructor
 
+
     public AjpNioProtocol() {
-        super(new NioEndpoint());
-        AjpConnectionHandler cHandler = new AjpConnectionHandler(this);
-        setHandler(cHandler);
-        ((NioEndpoint) getEndpoint()).setHandler(cHandler);
+        endpoint = new NioEndpoint();
+        cHandler = new AjpConnectionHandler(this);
+        ((NioEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+        // AJP does not use Send File
+        ((NioEndpoint) endpoint).setUseSendfile(false);
     }
 
 
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Connection handler for AJP.
+     */
+    private final AjpConnectionHandler cHandler;
+
+
     // ----------------------------------------------------- JMX related methods
 
     @Override
@@ -61,12 +87,20 @@
 
     // --------------------------------------  AjpConnectionHandler Inner Class
 
+
     protected static class AjpConnectionHandler
-            extends AbstractAjpConnectionHandler<NioChannel>
+            extends AbstractAjpConnectionHandler<NioChannel, AjpNioProcessor>
             implements Handler {
 
+        protected final AjpNioProtocol proto;
+
         public AjpConnectionHandler(AjpNioProtocol proto) {
-            super(proto);
+            this.proto = proto;
+        }
+
+        @Override
+        protected AbstractProtocol<NioChannel> getProtocol() {
+            return proto;
         }
 
         @Override
@@ -111,7 +145,7 @@
          * close, errors etc.
          */
         @Override
-        public void release(SocketWrapperBase<NioChannel> socket) {
+        public void release(SocketWrapper<NioChannel> socket) {
             Processor<NioChannel> processor =
                     connections.remove(socket.getSocket());
             if (processor != null) {
@@ -125,7 +159,7 @@
          * required.
          */
         @Override
-        public void release(SocketWrapperBase<NioChannel> socket,
+        public void release(SocketWrapper<NioChannel> socket,
                 Processor<NioChannel> processor, boolean isSocketClosing,
                 boolean addToPoller) {
             processor.recycle(isSocketClosing);
@@ -137,6 +171,14 @@
 
 
         @Override
+        protected AjpNioProcessor createProcessor() {
+            AjpNioProcessor processor = new AjpNioProcessor(proto.packetSize, (NioEndpoint)proto.endpoint);
+            proto.configureProcessor(processor);
+            register(processor);
+            return processor;
+        }
+
+        @Override
         public void onCreateSSLEngine(SSLEngine engine) {
         }
     }
diff --git a/java/org/apache/coyote/ajp/AjpProcessor.java b/java/org/apache/coyote/ajp/AjpProcessor.java
index 315d93a..adc090b 100644
--- a/java/org/apache/coyote/ajp/AjpProcessor.java
+++ b/java/org/apache/coyote/ajp/AjpProcessor.java
@@ -16,1743 +16,108 @@
  */
 package org.apache.coyote.ajp;
 
-import java.io.ByteArrayInputStream;
 import java.io.IOException;
-import java.io.InterruptedIOException;
-import java.net.InetAddress;
-import java.nio.ByteBuffer;
-import java.security.NoSuchProviderException;
-import java.security.cert.CertificateFactory;
-import java.security.cert.X509Certificate;
-import java.util.Iterator;
-import java.util.concurrent.LinkedBlockingDeque;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
 
-import javax.servlet.RequestDispatcher;
-import javax.servlet.http.HttpServletResponse;
-import javax.servlet.http.HttpUpgradeHandler;
-
-import org.apache.coyote.AbstractProcessor;
-import org.apache.coyote.ActionCode;
-import org.apache.coyote.AsyncContextCallback;
-import org.apache.coyote.ByteBufferHolder;
-import org.apache.coyote.ErrorState;
-import org.apache.coyote.InputBuffer;
-import org.apache.coyote.OutputBuffer;
-import org.apache.coyote.Request;
-import org.apache.coyote.RequestInfo;
-import org.apache.coyote.Response;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
-import org.apache.tomcat.util.ExceptionUtils;
-import org.apache.tomcat.util.buf.ByteChunk;
-import org.apache.tomcat.util.buf.HexUtils;
-import org.apache.tomcat.util.buf.MessageBytes;
-import org.apache.tomcat.util.http.HttpMessages;
-import org.apache.tomcat.util.http.MimeHeaders;
-import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
-import org.apache.tomcat.util.net.DispatchType;
-import org.apache.tomcat.util.net.SSLSupport;
-import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
-import org.apache.tomcat.util.res.StringManager;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
- * AJP Processor implementations.
+ * Processes AJP requests.
+ *
+ * @author Remy Maucherat
+ * @author Henri Gomez
+ * @author Dan Milstein
+ * @author Keith Wannamaker
+ * @author Kevin Seguin
+ * @author Costin Manolache
  */
-public class AjpProcessor<S> extends AbstractProcessor<S> {
+public class AjpProcessor extends AbstractAjpProcessor<Socket> {
 
     private static final Log log = LogFactory.getLog(AjpProcessor.class);
-    /**
-     * The string manager for this package.
-     */
-    protected static final StringManager sm = StringManager.getManager(AjpProcessor.class);
-
-
-    /**
-     * End message array.
-     */
-    protected static final byte[] endMessageArray;
-    protected static final byte[] endAndCloseMessageArray;
-
-
-    /**
-     * Flush message array.
-     */
-    protected static final byte[] flushMessageArray;
-
-
-    /**
-     * Pong message array.
-     */
-    protected static final byte[] pongMessageArray;
-
-
-    static {
-        // Allocate the end message array
-        AjpMessage endMessage = new AjpMessage(16);
-        endMessage.reset();
-        endMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
-        endMessage.appendByte(1);
-        endMessage.end();
-        endMessageArray = new byte[endMessage.getLen()];
-        System.arraycopy(endMessage.getBuffer(), 0, endMessageArray, 0,
-                endMessage.getLen());
-
-        // Allocate the end and close message array
-        AjpMessage endAndCloseMessage = new AjpMessage(16);
-        endAndCloseMessage.reset();
-        endAndCloseMessage.appendByte(Constants.JK_AJP13_END_RESPONSE);
-        endAndCloseMessage.appendByte(0);
-        endAndCloseMessage.end();
-        endAndCloseMessageArray = new byte[endAndCloseMessage.getLen()];
-        System.arraycopy(endAndCloseMessage.getBuffer(), 0, endAndCloseMessageArray, 0,
-                endAndCloseMessage.getLen());
-
-        // Allocate the flush message array
-        AjpMessage flushMessage = new AjpMessage(16);
-        flushMessage.reset();
-        flushMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
-        flushMessage.appendInt(0);
-        flushMessage.appendByte(0);
-        flushMessage.end();
-        flushMessageArray = new byte[flushMessage.getLen()];
-        System.arraycopy(flushMessage.getBuffer(), 0, flushMessageArray, 0,
-                flushMessage.getLen());
-
-        // Allocate the pong message array
-        AjpMessage pongMessage = new AjpMessage(16);
-        pongMessage.reset();
-        pongMessage.appendByte(Constants.JK_AJP13_CPONG_REPLY);
-        pongMessage.end();
-        pongMessageArray = new byte[pongMessage.getLen()];
-        System.arraycopy(pongMessage.getBuffer(), 0, pongMessageArray,
-                0, pongMessage.getLen());
-    }
-
-
-    // ----------------------------------------------------- Instance Variables
-
-
-    /**
-     * GetBody message array. Not static like the other message arrays since the
-     * message varies with packetSize and that can vary per connector.
-     */
-    protected final byte[] getBodyMessageArray;
-
-
-    /**
-     * AJP packet size.
-     */
-    private final int outputMaxChunkSize;
-
-    /**
-     * Header message. Note that this header is merely the one used during the
-     * processing of the first message of a "request", so it might not be a
-     * request header. It will stay unchanged during the processing of the whole
-     * request.
-     */
-    protected final AjpMessage requestHeaderMessage;
-
-
-    /**
-     * Message used for response composition.
-     */
-    protected final AjpMessage responseMessage;
-
-
-    /**
-     * Location of next write of the response message (used with non-blocking
-     * writes when the message may not be written in a single write). A value of
-     * -1 indicates that no message has been written to the buffer.
-     */
-    private int responseMsgPos = -1;
-
-
-    /**
-     * Body message.
-     */
-    protected final AjpMessage bodyMessage;
-
-
-    /**
-     * Body message.
-     */
-    protected final MessageBytes bodyBytes = MessageBytes.newInstance();
-
-
-    /**
-     * The max size of the buffered write buffer
-     */
-    private int bufferedWriteSize = 64*1024; //64k default write buffer
-
-
-    /**
-     * For "non-blocking" writes use an external set of buffers. Although the
-     * API only allows one non-blocking write at a time, due to buffering and
-     * the possible need to write HTTP headers, there may be more than one write
-     * to the OutputBuffer.
-     */
-    private final LinkedBlockingDeque<ByteBufferHolder> bufferedWrites =
-            new LinkedBlockingDeque<>();
-
-
-    /**
-     * Host name (used to avoid useless B2C conversion on the host name).
-     */
-    protected char[] hostNameC = new char[0];
-
-
-    /**
-     * Temp message bytes used for processing.
-     */
-    protected final MessageBytes tmpMB = MessageBytes.newInstance();
-
-
-    /**
-     * Byte chunk for certs.
-     */
-    protected final MessageBytes certificates = MessageBytes.newInstance();
-
-
-    /**
-     * End of stream flag.
-     */
-    protected boolean endOfStream = false;
-
-
-    /**
-     * Request body empty flag.
-     */
-    protected boolean empty = true;
-
-
-    /**
-     * First read.
-     */
-    protected boolean first = true;
-
-
-    /**
-     * Indicates that a 'get body chunk' message has been sent but the body
-     * chunk has not yet been received.
-     */
-    private boolean waitingForBodyMessage = false;
-
-
-    /**
-     * Replay read.
-     */
-    protected boolean replay = false;
-
-
-    /**
-     * Should any response body be swallowed and not sent to the client.
-     */
-    private boolean swallowResponse = false;
-
-
-    /**
-     * Finished response.
-     */
-    protected boolean finished = false;
-
-
-    /**
-     * Bytes written to client for the current request.
-     */
-    protected long bytesWritten = 0;
-
-
-    // ------------------------------------------------------------ Constructor
-
-    public AjpProcessor(int packetSize, AbstractEndpoint<S> endpoint) {
-
-        super(endpoint);
-
-        // Calculate maximum chunk size as packetSize may have been changed from
-        // the default (Constants.MAX_PACKET_SIZE)
-        this.outputMaxChunkSize =
-                Constants.MAX_SEND_SIZE + packetSize - Constants.MAX_PACKET_SIZE;
-
-        request.setInputBuffer(new SocketInputBuffer());
-
-        requestHeaderMessage = new AjpMessage(packetSize);
-        responseMessage = new AjpMessage(packetSize);
-        bodyMessage = new AjpMessage(packetSize);
-
-        // Set the getBody message buffer
-        AjpMessage getBodyMessage = new AjpMessage(16);
-        getBodyMessage.reset();
-        getBodyMessage.appendByte(Constants.JK_AJP13_GET_BODY_CHUNK);
-        // Adjust read size if packetSize != default (Constants.MAX_PACKET_SIZE)
-        getBodyMessage.appendInt(Constants.MAX_READ_SIZE + packetSize -
-                Constants.MAX_PACKET_SIZE);
-        getBodyMessage.end();
-        getBodyMessageArray = new byte[getBodyMessage.getLen()];
-        System.arraycopy(getBodyMessage.getBuffer(), 0, getBodyMessageArray,
-                0, getBodyMessage.getLen());
-
-        response.setOutputBuffer(new SocketOutputBuffer());
-    }
-
-
-    // ------------------------------------------------------------- Properties
-
-
-    /**
-     * The number of milliseconds Tomcat will wait for a subsequent request
-     * before closing the connection. The default is -1 which is an infinite
-     * timeout.
-     */
-    protected int keepAliveTimeout = -1;
-    public int getKeepAliveTimeout() { return keepAliveTimeout; }
-    public void setKeepAliveTimeout(int timeout) { keepAliveTimeout = timeout; }
-
-
-    /**
-     * Use Tomcat authentication ?
-     */
-    protected boolean tomcatAuthentication = true;
-    public boolean getTomcatAuthentication() { return tomcatAuthentication; }
-    public void setTomcatAuthentication(boolean tomcatAuthentication) {
-        this.tomcatAuthentication = tomcatAuthentication;
-    }
-
-
-    /**
-     * Required secret.
-     */
-    protected String requiredSecret = null;
-    public void setRequiredSecret(String requiredSecret) {
-        this.requiredSecret = requiredSecret;
-    }
-
-
-    /**
-     * When client certificate information is presented in a form other than
-     * instances of {@link java.security.cert.X509Certificate} it needs to be
-     * converted before it can be used and this property controls which JSSE
-     * provider is used to perform the conversion. For example it is used with
-     * the AJP connectors, the HTTP APR connector and with the
-     * {@link org.apache.catalina.valves.SSLValve}. If not specified, the
-     * default provider will be used.
-     */
-    protected String clientCertProvider = null;
-    public String getClientCertProvider() { return clientCertProvider; }
-    public void setClientCertProvider(String s) { this.clientCertProvider = s; }
-
-    // --------------------------------------------------------- Public Methods
-
-
-    /**
-     * Send an action to the connector.
-     *
-     * @param actionCode Type of the action
-     * @param param Action parameter
-     */
-    @Override
-    public final void action(ActionCode actionCode, Object param) {
-
-        switch (actionCode) {
-        case CLOSE: {
-            // End the processing of the current request, and stop any further
-            // transactions with the client
-
-            try {
-                finish();
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_NOW, e);
-            }
-            break;
-        }
-        case COMMIT: {
-            if (response.isCommitted())
-                return;
-
-            // Validate and write response headers
-            try {
-                prepareResponse();
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_NOW, e);
-            }
-
-            try {
-                flush(false);
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_NOW, e);
-            }
-            break;
-        }
-        case ACK: {
-            // NO_OP for AJP
-            break;
-        }
-        case CLIENT_FLUSH: {
-            if (!response.isCommitted()) {
-                // Validate and write response headers
-                try {
-                    prepareResponse();
-                } catch (IOException e) {
-                    setErrorState(ErrorState.CLOSE_NOW, e);
-                    return;
-                }
-            }
-
-            try {
-                flush(true);
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_NOW, e);
-            }
-            break;
-        }
-        case IS_ERROR: {
-            ((AtomicBoolean) param).set(getErrorState().isError());
-            break;
-        }
-        case DISABLE_SWALLOW_INPUT: {
-            // TODO: Do not swallow request input but
-            // make sure we are closing the connection
-            setErrorState(ErrorState.CLOSE_CLEAN, null);
-            break;
-        }
-        case RESET: {
-            // NO-OP
-            break;
-        }
-        case REQ_SSL_ATTRIBUTE: {
-            if (!certificates.isNull()) {
-                ByteChunk certData = certificates.getByteChunk();
-                X509Certificate jsseCerts[] = null;
-                ByteArrayInputStream bais =
-                    new ByteArrayInputStream(certData.getBytes(),
-                            certData.getStart(),
-                            certData.getLength());
-                // Fill the  elements.
-                try {
-                    CertificateFactory cf;
-                    if (clientCertProvider == null) {
-                        cf = CertificateFactory.getInstance("X.509");
-                    } else {
-                        cf = CertificateFactory.getInstance("X.509",
-                                clientCertProvider);
-                    }
-                    while(bais.available() > 0) {
-                        X509Certificate cert = (X509Certificate)
-                        cf.generateCertificate(bais);
-                        if(jsseCerts == null) {
-                            jsseCerts = new X509Certificate[1];
-                            jsseCerts[0] = cert;
-                        } else {
-                            X509Certificate [] temp = new X509Certificate[jsseCerts.length+1];
-                            System.arraycopy(jsseCerts,0,temp,0,jsseCerts.length);
-                            temp[jsseCerts.length] = cert;
-                            jsseCerts = temp;
-                        }
-                    }
-                } catch (java.security.cert.CertificateException e) {
-                    getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
-                    return;
-                } catch (NoSuchProviderException e) {
-                    getLog().error(sm.getString("ajpprocessor.certs.fail"), e);
-                    return;
-                }
-                request.setAttribute(SSLSupport.CERTIFICATE_KEY, jsseCerts);
-            }
-            break;
-        }
-        case REQ_SSL_CERTIFICATE: {
-            // NO-OP. Can't force a new SSL handshake with the client when using
-            // AJP as the reverse proxy controls that connection.
-            break;
-        }
-        case REQ_HOST_ATTRIBUTE: {
-            // Get remote host name using a DNS resolution
-            if (request.remoteHost().isNull()) {
-                try {
-                    request.remoteHost().setString(InetAddress.getByName
-                            (request.remoteAddr().toString()).getHostName());
-                } catch (IOException iex) {
-                    // Ignore
-                }
-            }
-            break;
-        }
-        case REQ_HOST_ADDR_ATTRIBUTE: {
-            // NO-OP
-            // Automatically populated during prepareRequest()
-            break;
-        }
-        case REQ_LOCAL_NAME_ATTRIBUTE: {
-            // NO-OP
-            // Automatically populated during prepareRequest()
-            break;
-        }
-        case REQ_LOCAL_ADDR_ATTRIBUTE: {
-            // Automatically populated during prepareRequest() when using
-            // modern AJP forwarder, otherwise copy from local name
-            if (request.localAddr().isNull()) {
-                request.localAddr().setString(request.localName().toString());
-            }
-            break;
-        }
-        case REQ_REMOTEPORT_ATTRIBUTE: {
-            // NO-OP
-            // Automatically populated during prepareRequest() when using
-            // modern AJP forwarder, otherwise not available
-            break;
-        }
-        case REQ_LOCALPORT_ATTRIBUTE: {
-            // NO-OP
-            // Automatically populated during prepareRequest()
-            break;
-        }
-        case REQ_SET_BODY_REPLAY: {
-            // Set the given bytes as the content
-            ByteChunk bc = (ByteChunk) param;
-            int length = bc.getLength();
-            bodyBytes.setBytes(bc.getBytes(), bc.getStart(), length);
-            request.setContentLength(length);
-            first = false;
-            empty = false;
-            replay = true;
-            endOfStream = false;
-            break;
-        }
-        case ASYNC_START: {
-            asyncStateMachine.asyncStart((AsyncContextCallback) param);
-            break;
-        }
-        case ASYNC_COMPLETE: {
-            socketWrapper.clearDispatches();
-            if (asyncStateMachine.asyncComplete()) {
-                endpoint.processSocket(socketWrapper, SocketStatus.OPEN_READ, true);
-            }
-            break;
-        }
-        case ASYNC_DISPATCH: {
-            if (asyncStateMachine.asyncDispatch()) {
-                endpoint.processSocket(socketWrapper, SocketStatus.OPEN_READ, true);
-            }
-            break;
-        }
-        case ASYNC_DISPATCHED: {
-            asyncStateMachine.asyncDispatched();
-            break;
-        }
-        case ASYNC_SETTIMEOUT: {
-            if (param == null) return;
-            long timeout = ((Long)param).longValue();
-            socketWrapper.setAsyncTimeout(timeout);
-            break;
-        }
-        case ASYNC_TIMEOUT: {
-            AtomicBoolean result = (AtomicBoolean) param;
-            result.set(asyncStateMachine.asyncTimeout());
-            break;
-        }
-        case ASYNC_RUN: {
-            asyncStateMachine.asyncRun((Runnable) param);
-            break;
-        }
-        case ASYNC_ERROR: {
-            asyncStateMachine.asyncError();
-            break;
-        }
-        case ASYNC_IS_STARTED: {
-            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncStarted());
-            break;
-        }
-        case ASYNC_IS_COMPLETING: {
-            ((AtomicBoolean) param).set(asyncStateMachine.isCompleting());
-            break;
-        }
-        case ASYNC_IS_DISPATCHING: {
-            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncDispatching());
-            break;
-        }
-        case ASYNC_IS_ASYNC: {
-            ((AtomicBoolean) param).set(asyncStateMachine.isAsync());
-            break;
-        }
-        case ASYNC_IS_TIMINGOUT: {
-            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncTimingOut());
-            break;
-        }
-        case ASYNC_IS_ERROR: {
-            ((AtomicBoolean) param).set(asyncStateMachine.isAsyncError());
-            break;
-        }
-        case UPGRADE: {
-            // HTTP connections only. Unsupported for AJP.
-            throw new UnsupportedOperationException(
-                    sm.getString("ajpprocessor.httpupgrade.notsupported"));
-        }
-        case AVAILABLE: {
-            if (available()) {
-                request.setAvailable(1);
-            } else {
-                request.setAvailable(0);
-            }
-            break;
-        }
-        case NB_READ_INTEREST: {
-            if (!endOfStream) {
-                registerForEvent(true, false);
-            }
-            break;
-        }
-        case NB_WRITE_INTEREST: {
-            AtomicBoolean isReady = (AtomicBoolean)param;
-            boolean result = bufferedWrites.size() == 0 && responseMsgPos == -1;
-            isReady.set(result);
-            if (!result) {
-                registerForEvent(false, true);
-            }
-            break;
-        }
-        case REQUEST_BODY_FULLY_READ: {
-            AtomicBoolean result = (AtomicBoolean) param;
-            result.set(endOfStream);
-            break;
-        }
-        case DISPATCH_READ: {
-            socketWrapper.addDispatch(DispatchType.NON_BLOCKING_READ);
-            break;
-        }
-        case DISPATCH_WRITE: {
-            socketWrapper.addDispatch(DispatchType.NON_BLOCKING_WRITE);
-            break;
-        }
-        case DISPATCH_EXECUTE: {
-            getEndpoint().executeNonBlockingDispatches(socketWrapper);
-            break;
-        }
-        case CLOSE_NOW: {
-            // Prevent further writes to the response
-            swallowResponse = true;
-            setErrorState(ErrorState.CLOSE_NOW, null);
-            break;
-        }
-        }
-    }
-
-
-    @Override
-    public SocketState asyncDispatch(SocketStatus status) {
-
-        if (status == SocketStatus.OPEN_WRITE) {
-            try {
-                asyncStateMachine.asyncOperation();
-                try {
-                    if (hasDataToWrite()) {
-                        flushBufferedData();
-                        if (hasDataToWrite()) {
-                            // There is data to write but go via Response to
-                            // maintain a consistent view of non-blocking state
-                            response.checkRegisterForWrite(true);
-                            return SocketState.LONG;
-                        }
-                    }
-                } catch (IOException x) {
-                    if (getLog().isDebugEnabled()) {
-                        getLog().debug("Unable to write async data.",x);
-                    }
-                    status = SocketStatus.ASYNC_WRITE_ERROR;
-                    request.setAttribute(RequestDispatcher.ERROR_EXCEPTION, x);
-                }
-            } catch (IllegalStateException x) {
-                registerForEvent(false, true);
-            }
-        } else if (status == SocketStatus.OPEN_READ &&
-                request.getReadListener() != null) {
-            try {
-                if (available()) {
-                    asyncStateMachine.asyncOperation();
-                }
-            } catch (IllegalStateException x) {
-                registerForEvent(true, false);
-            }
-        }
-
-        RequestInfo rp = request.getRequestProcessor();
-        try {
-            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
-            if(!getAdapter().asyncDispatch(request, response, status)) {
-                setErrorState(ErrorState.CLOSE_NOW, null);
-            }
-        } catch (InterruptedIOException e) {
-            setErrorState(ErrorState.CLOSE_NOW, e);
-        } catch (Throwable t) {
-            ExceptionUtils.handleThrowable(t);
-            setErrorState(ErrorState.CLOSE_NOW, t);
-            getLog().error(sm.getString("http11processor.request.process"), t);
-        }
-
-        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
-
-        if (isAsync()) {
-            if (getErrorState().isError()) {
-                request.updateCounters();
-                return SocketState.CLOSED;
-            } else {
-                return SocketState.LONG;
-            }
-        } else {
-            // Set keep alive timeout for next request if enabled
-            if (keepAliveTimeout > 0) {
-                socketWrapper.setTimeout(keepAliveTimeout);
-            }
-            request.updateCounters();
-            if (getErrorState().isError()) {
-                return SocketState.CLOSED;
-            } else {
-                return SocketState.OPEN;
-            }
-        }
-    }
-
-
-    /**
-     * Process pipelined HTTP requests using the specified input and output
-     * streams.
-     *
-     * @throws IOException error during an I/O operation
-     */
-    @Override
-    public SocketState process(SocketWrapperBase<S> socket) throws IOException {
-
-        RequestInfo rp = request.getRequestProcessor();
-        rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
-
-        // Setting up the socket
-        this.socketWrapper = socket;
-
-        int soTimeout = endpoint.getSoTimeout();
-        boolean cping = false;
-
-        boolean keptAlive = false;
-
-        while (!getErrorState().isError() && !endpoint.isPaused()) {
-            // Parsing the request header
-            try {
-                // Get first message of the request
-                if (!readMessage(requestHeaderMessage, !keptAlive)) {
-                    break;
-                }
-                // Set back timeout if keep alive timeout is enabled
-                if (keepAliveTimeout > 0) {
-                    socketWrapper.setTimeout(soTimeout);
-                }
-                // Check message type, process right away and break if
-                // not regular request processing
-                int type = requestHeaderMessage.getByte();
-                if (type == Constants.JK_AJP13_CPING_REQUEST) {
-                    if (endpoint.isPaused()) {
-                        recycle(true);
-                        break;
-                    }
-                    cping = true;
-                    try {
-                        output(pongMessageArray, 0, pongMessageArray.length, true);
-                    } catch (IOException e) {
-                        setErrorState(ErrorState.CLOSE_NOW, e);
-                    }
-                    recycle(false);
-                    continue;
-                } else if(type != Constants.JK_AJP13_FORWARD_REQUEST) {
-                    // Unexpected packet type. Unread body packets should have
-                    // been swallowed in finish().
-                    if (getLog().isDebugEnabled()) {
-                        getLog().debug("Unexpected message: " + type);
-                    }
-                    setErrorState(ErrorState.CLOSE_NOW, null);
-                    break;
-                }
-                keptAlive = true;
-                request.setStartTime(System.currentTimeMillis());
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_NOW, e);
-                break;
-            } catch (Throwable t) {
-                ExceptionUtils.handleThrowable(t);
-                getLog().debug(sm.getString("ajpprocessor.header.error"), t);
-                // 400 - Bad Request
-                response.setStatus(400);
-                setErrorState(ErrorState.CLOSE_CLEAN, t);
-                getAdapter().log(request, response, 0);
-            }
-
-            if (!getErrorState().isError()) {
-                // Setting up filters, and parse some request headers
-                rp.setStage(org.apache.coyote.Constants.STAGE_PREPARE);
-                try {
-                    prepareRequest();
-                } catch (Throwable t) {
-                    ExceptionUtils.handleThrowable(t);
-                    getLog().debug(sm.getString("ajpprocessor.request.prepare"), t);
-                    // 500 - Internal Server Error
-                    response.setStatus(500);
-                    setErrorState(ErrorState.CLOSE_CLEAN, t);
-                    getAdapter().log(request, response, 0);
-                }
-            }
-
-            if (!getErrorState().isError() && !cping && endpoint.isPaused()) {
-                // 503 - Service unavailable
-                response.setStatus(503);
-                setErrorState(ErrorState.CLOSE_CLEAN, null);
-                getAdapter().log(request, response, 0);
-            }
-            cping = false;
-
-            // Process the request in the adapter
-            if (!getErrorState().isError()) {
-                try {
-                    rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
-                    getAdapter().service(request, response);
-                } catch (InterruptedIOException e) {
-                    setErrorState(ErrorState.CLOSE_NOW, e);
-                } catch (Throwable t) {
-                    ExceptionUtils.handleThrowable(t);
-                    getLog().error(sm.getString("ajpprocessor.request.process"), t);
-                    // 500 - Internal Server Error
-                    response.setStatus(500);
-                    setErrorState(ErrorState.CLOSE_CLEAN, t);
-                    getAdapter().log(request, response, 0);
-                }
-            }
-
-            if (isAsync() && !getErrorState().isError()) {
-                break;
-            }
-
-            // Finish the response if not done yet
-            if (!finished && getErrorState().isIoAllowed()) {
-                try {
-                    finish();
-                } catch (Throwable t) {
-                    ExceptionUtils.handleThrowable(t);
-                    setErrorState(ErrorState.CLOSE_NOW, t);
-                }
-            }
-
-            // If there was an error, make sure the request is counted as
-            // and error, and update the statistics counter
-            if (getErrorState().isError()) {
-                response.setStatus(500);
-            }
-            request.updateCounters();
-
-            rp.setStage(org.apache.coyote.Constants.STAGE_KEEPALIVE);
-            // Set keep alive timeout for next request if enabled
-            if (keepAliveTimeout > 0) {
-                socketWrapper.setTimeout(keepAliveTimeout);
-            }
-
-            recycle(false);
-        }
-
-        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
-
-        if (getErrorState().isError() || endpoint.isPaused()) {
-            return SocketState.CLOSED;
-        } else {
-            if (isAsync()) {
-                return SocketState.LONG;
-            } else {
-                return SocketState.OPEN;
-            }
-        }
-    }
-
-
-    @Override
-    public void setSslSupport(SSLSupport sslSupport) {
-        // Should never reach this code but in case we do...
-        throw new IllegalStateException(
-                sm.getString("ajpprocessor.ssl.notsupported"));
-    }
-
-
-    @Override
-    public SocketState upgradeDispatch(SocketStatus status) throws IOException {
-        // Should never reach this code but in case we do...
-        throw new IOException(
-                sm.getString("ajpprocessor.httpupgrade.notsupported"));
-    }
-
-
-    @Override
-    public HttpUpgradeHandler getHttpUpgradeHandler() {
-        // Should never reach this code but in case we do...
-        throw new IllegalStateException(
-                sm.getString("ajpprocessor.httpupgrade.notsupported"));
-    }
-
-
-    /**
-     * Recycle the processor, ready for the next request which may be on the
-     * same connection or a different connection.
-     *
-     * @param socketClosing Indicates if the socket is about to be closed
-     *                      allowing the processor to perform any additional
-     *                      clean-up that may be required
-     */
-    @Override
-    public void recycle(boolean socketClosing) {
-        getAdapter().checkRecycled(request, response);
-
-        asyncStateMachine.recycle();
-
-        // Recycle Request object
-        first = true;
-        endOfStream = false;
-        waitingForBodyMessage = false;
-        empty = true;
-        replay = false;
-        finished = false;
-        request.recycle();
-        response.recycle();
-        certificates.recycle();
-        swallowResponse = false;
-        bytesWritten = 0;
-        resetErrorState();
-    }
-
-
-    // ------------------------------------------------------ Protected Methods
-
-    // Methods used by SocketInputBuffer
-    /**
-     * Read an AJP body message. Used to read both the 'special' packet in ajp13
-     * and to receive the data after we send a GET_BODY packet.
-     *
-     * @param block If there is no data available to read when this method is
-     *              called, should this call block until data becomes available?
-     *
-     * @return <code>true</code> if at least one body byte was read, otherwise
-     *         <code>false</code>
-     */
-    protected boolean receive(boolean block) throws IOException {
-
-        bodyMessage.reset();
-
-        if (!readMessage(bodyMessage, block)) {
-            return false;
-        }
-
-        waitingForBodyMessage = false;
-
-        // No data received.
-        if (bodyMessage.getLen() == 0) {
-            // just the header
-            return false;
-        }
-        int blen = bodyMessage.peekInt();
-        if (blen == 0) {
-            return false;
-        }
-
-        bodyMessage.getBodyBytes(bodyBytes);
-        empty = false;
-        return true;
-    }
-
-
-    /**
-     * Read an AJP message.
-     *
-     * @param message   The message to populate
-     * @param block If there is no data available to read when this method is
-     *              called, should this call block until data becomes available?
-
-     * @return true if the message has been read, false if no data was read
-     *
-     * @throws IOException any other failure, including incomplete reads
-     */
-    protected boolean readMessage(AjpMessage message, boolean block)
-        throws IOException {
-
-        byte[] buf = message.getBuffer();
-
-        if (!read(buf, 0, Constants.H_SIZE, block)) {
-            return false;
-        }
-
-        int messageLength = message.processHeader(true);
-        if (messageLength < 0) {
-            // Invalid AJP header signature
-            throw new IOException(sm.getString("ajpmessage.invalidLength",
-                    Integer.valueOf(messageLength)));
-        }
-        else if (messageLength == 0) {
-            // Zero length message.
-            return true;
-        }
-        else {
-            if (messageLength > message.getBuffer().length) {
-                // Message too long for the buffer
-                // Need to trigger a 400 response
-                throw new IllegalArgumentException(sm.getString(
-                        "ajpprocessor.header.tooLong",
-                        Integer.valueOf(messageLength),
-                        Integer.valueOf(buf.length)));
-            }
-            read(buf, Constants.H_SIZE, messageLength, true);
-            return true;
-        }
-    }
-
-
-    @Override
-    public final boolean isUpgrade() {
-        // AJP does not support HTTP upgrade
-        return false;
-    }
-
-
-    @Override
-    public ByteBuffer getLeftoverInput() {
-        return null;
-    }
-
-
-    /**
-     * Get more request body data from the web server and store it in the
-     * internal buffer.
-     *
-     * @return true if there is more data, false if not.
-     */
-    protected boolean refillReadBuffer(boolean block) throws IOException {
-        // When using replay (e.g. after FORM auth) all the data to read has
-        // been buffered so there is no opportunity to refill the buffer.
-        if (replay) {
-            endOfStream = true; // we've read everything there is
-        }
-        if (endOfStream) {
-            return false;
-        }
-
-        if (first) {
-            first = false;
-            long contentLength = request.getContentLengthLong();
-            // - When content length > 0, AJP sends the first body message
-            //   automatically.
-            // - When content length == 0, AJP does not send a body message.
-            // - When content length is unknown, AJP does not send the first
-            //   body message automatically.
-            if (contentLength > 0) {
-                waitingForBodyMessage = true;
-            } else if (contentLength == 0) {
-                endOfStream = true;
-                return false;
-            }
-        }
-
-        // Request more data immediately
-        if (!waitingForBodyMessage) {
-            output(getBodyMessageArray, 0, getBodyMessageArray.length, true);
-            waitingForBodyMessage = true;
-        }
-
-        boolean moreData = receive(block);
-        if (!moreData && !waitingForBodyMessage) {
-            endOfStream = true;
-        }
-        return moreData;
-    }
-
-
-    /**
-     * After reading the request headers, we have to setup the request filters.
-     */
-    protected void prepareRequest() {
-
-        // Translate the HTTP method code to a String.
-        byte methodCode = requestHeaderMessage.getByte();
-        if (methodCode != Constants.SC_M_JK_STORED) {
-            String methodName = Constants.getMethodForCode(methodCode - 1);
-            request.method().setString(methodName);
-        }
-
-        requestHeaderMessage.getBytes(request.protocol());
-        requestHeaderMessage.getBytes(request.requestURI());
-
-        requestHeaderMessage.getBytes(request.remoteAddr());
-        requestHeaderMessage.getBytes(request.remoteHost());
-        requestHeaderMessage.getBytes(request.localName());
-        request.setLocalPort(requestHeaderMessage.getInt());
-
-        boolean isSSL = requestHeaderMessage.getByte() != 0;
-        if (isSSL) {
-            request.scheme().setString("https");
-        }
-
-        // Decode headers
-        MimeHeaders headers = request.getMimeHeaders();
-
-        // Set this every time in case limit has been changed via JMX
-        headers.setLimit(endpoint.getMaxHeaderCount());
-
-        boolean contentLengthSet = false;
-        int hCount = requestHeaderMessage.getInt();
-        for(int i = 0 ; i < hCount ; i++) {
-            String hName = null;
-
-            // Header names are encoded as either an integer code starting
-            // with 0xA0, or as a normal string (in which case the first
-            // two bytes are the length).
-            int isc = requestHeaderMessage.peekInt();
-            int hId = isc & 0xFF;
-
-            MessageBytes vMB = null;
-            isc &= 0xFF00;
-            if(0xA000 == isc) {
-                requestHeaderMessage.getInt(); // To advance the read position
-                hName = Constants.getHeaderForCode(hId - 1);
-                vMB = headers.addValue(hName);
-            } else {
-                // reset hId -- if the header currently being read
-                // happens to be 7 or 8 bytes long, the code below
-                // will think it's the content-type header or the
-                // content-length header - SC_REQ_CONTENT_TYPE=7,
-                // SC_REQ_CONTENT_LENGTH=8 - leading to unexpected
-                // behaviour.  see bug 5861 for more information.
-                hId = -1;
-                requestHeaderMessage.getBytes(tmpMB);
-                ByteChunk bc = tmpMB.getByteChunk();
-                vMB = headers.addValue(bc.getBuffer(),
-                        bc.getStart(), bc.getLength());
-            }
-
-            requestHeaderMessage.getBytes(vMB);
-
-            if (hId == Constants.SC_REQ_CONTENT_LENGTH ||
-                    (hId == -1 && tmpMB.equalsIgnoreCase("Content-Length"))) {
-                long cl = vMB.getLong();
-                if (contentLengthSet) {
-                    response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
-                    setErrorState(ErrorState.CLOSE_CLEAN, null);
-                } else {
-                    contentLengthSet = true;
-                    // Set the content-length header for the request
-                    request.setContentLength(cl);
-                }
-            } else if (hId == Constants.SC_REQ_CONTENT_TYPE ||
-                    (hId == -1 && tmpMB.equalsIgnoreCase("Content-Type"))) {
-                // just read the content-type header, so set it
-                ByteChunk bchunk = vMB.getByteChunk();
-                request.contentType().setBytes(bchunk.getBytes(),
-                        bchunk.getOffset(),
-                        bchunk.getLength());
-            }
-        }
-
-        // Decode extra attributes
-        boolean secret = false;
-        byte attributeCode;
-        while ((attributeCode = requestHeaderMessage.getByte())
-                != Constants.SC_A_ARE_DONE) {
-
-            switch (attributeCode) {
-
-            case Constants.SC_A_REQ_ATTRIBUTE :
-                requestHeaderMessage.getBytes(tmpMB);
-                String n = tmpMB.toString();
-                requestHeaderMessage.getBytes(tmpMB);
-                String v = tmpMB.toString();
-                /*
-                 * AJP13 misses to forward the local IP address and the
-                 * remote port. Allow the AJP connector to add this info via
-                 * private request attributes.
-                 * We will accept the forwarded data and remove it from the
-                 * public list of request attributes.
-                 */
-                if(n.equals(Constants.SC_A_REQ_LOCAL_ADDR)) {
-                    request.localAddr().setString(v);
-                } else if(n.equals(Constants.SC_A_REQ_REMOTE_PORT)) {
-                    try {
-                        request.setRemotePort(Integer.parseInt(v));
-                    } catch (NumberFormatException nfe) {
-                        // Ignore invalid value
-                    }
-                } else {
-                    request.setAttribute(n, v );
-                }
-                break;
-
-            case Constants.SC_A_CONTEXT :
-                requestHeaderMessage.getBytes(tmpMB);
-                // nothing
-                break;
-
-            case Constants.SC_A_SERVLET_PATH :
-                requestHeaderMessage.getBytes(tmpMB);
-                // nothing
-                break;
-
-            case Constants.SC_A_REMOTE_USER :
-                if (tomcatAuthentication) {
-                    // ignore server
-                    requestHeaderMessage.getBytes(tmpMB);
-                } else {
-                    requestHeaderMessage.getBytes(request.getRemoteUser());
-                }
-                break;
-
-            case Constants.SC_A_AUTH_TYPE :
-                if (tomcatAuthentication) {
-                    // ignore server
-                    requestHeaderMessage.getBytes(tmpMB);
-                } else {
-                    requestHeaderMessage.getBytes(request.getAuthType());
-                }
-                break;
-
-            case Constants.SC_A_QUERY_STRING :
-                requestHeaderMessage.getBytes(request.queryString());
-                break;
-
-            case Constants.SC_A_JVM_ROUTE :
-                requestHeaderMessage.getBytes(request.instanceId());
-                break;
-
-            case Constants.SC_A_SSL_CERT :
-                // SSL certificate extraction is lazy, moved to JkCoyoteHandler
-                requestHeaderMessage.getBytes(certificates);
-                break;
-
-            case Constants.SC_A_SSL_CIPHER :
-                requestHeaderMessage.getBytes(tmpMB);
-                request.setAttribute(SSLSupport.CIPHER_SUITE_KEY,
-                        tmpMB.toString());
-                break;
-
-            case Constants.SC_A_SSL_SESSION :
-                requestHeaderMessage.getBytes(tmpMB);
-                request.setAttribute(SSLSupport.SESSION_ID_KEY,
-                        tmpMB.toString());
-                break;
-
-            case Constants.SC_A_SSL_KEY_SIZE :
-                request.setAttribute(SSLSupport.KEY_SIZE_KEY,
-                        Integer.valueOf(requestHeaderMessage.getInt()));
-                break;
-
-            case Constants.SC_A_STORED_METHOD:
-                requestHeaderMessage.getBytes(request.method());
-                break;
-
-            case Constants.SC_A_SECRET:
-                requestHeaderMessage.getBytes(tmpMB);
-                if (requiredSecret != null) {
-                    secret = true;
-                    if (!tmpMB.equals(requiredSecret)) {
-                        response.setStatus(403);
-                        setErrorState(ErrorState.CLOSE_CLEAN, null);
-                    }
-                }
-                break;
-
-            default:
-                // Ignore unknown attribute for backward compatibility
-                break;
-
-            }
-
-        }
-
-        // Check if secret was submitted if required
-        if ((requiredSecret != null) && !secret) {
-            response.setStatus(403);
-            setErrorState(ErrorState.CLOSE_CLEAN, null);
-        }
-
-        // Check for a full URI (including protocol://host:port/)
-        ByteChunk uriBC = request.requestURI().getByteChunk();
-        if (uriBC.startsWithIgnoreCase("http", 0)) {
-
-            int pos = uriBC.indexOf("://", 0, 3, 4);
-            int uriBCStart = uriBC.getStart();
-            int slashPos = -1;
-            if (pos != -1) {
-                byte[] uriB = uriBC.getBytes();
-                slashPos = uriBC.indexOf('/', pos + 3);
-                if (slashPos == -1) {
-                    slashPos = uriBC.getLength();
-                    // Set URI as "/"
-                    request.requestURI().setBytes
-                    (uriB, uriBCStart + pos + 1, 1);
-                } else {
-                    request.requestURI().setBytes
-                    (uriB, uriBCStart + slashPos,
-                            uriBC.getLength() - slashPos);
-                }
-                MessageBytes hostMB = headers.setValue("host");
-                hostMB.setBytes(uriB, uriBCStart + pos + 3,
-                        slashPos - pos - 3);
-            }
-
-        }
-
-        MessageBytes valueMB = request.getMimeHeaders().getValue("host");
-        parseHost(valueMB);
-
-        if (getErrorState().isError()) {
-            getAdapter().log(request, response, 0);
-        }
-    }
-
-
-    /**
-     * Parse host.
-     */
-    protected void parseHost(MessageBytes valueMB) {
-
-        if (valueMB == null || valueMB.isNull()) {
-            // HTTP/1.0
-            request.setServerPort(request.getLocalPort());
-            try {
-                request.serverName().duplicate(request.localName());
-            } catch (IOException e) {
-                response.setStatus(400);
-                setErrorState(ErrorState.CLOSE_CLEAN, e);
-            }
-            return;
-        }
-
-        ByteChunk valueBC = valueMB.getByteChunk();
-        byte[] valueB = valueBC.getBytes();
-        int valueL = valueBC.getLength();
-        int valueS = valueBC.getStart();
-        int colonPos = -1;
-        if (hostNameC.length < valueL) {
-            hostNameC = new char[valueL];
-        }
-
-        boolean ipv6 = (valueB[valueS] == '[');
-        boolean bracketClosed = false;
-        for (int i = 0; i < valueL; i++) {
-            char b = (char) valueB[i + valueS];
-            hostNameC[i] = b;
-            if (b == ']') {
-                bracketClosed = true;
-            } else if (b == ':') {
-                if (!ipv6 || bracketClosed) {
-                    colonPos = i;
-                    break;
-                }
-            }
-        }
-
-        if (colonPos < 0) {
-            if (request.scheme().equalsIgnoreCase("https")) {
-                // 443 - Default HTTPS port
-                request.setServerPort(443);
-            } else {
-                // 80 - Default HTTTP port
-                request.setServerPort(80);
-            }
-            request.serverName().setChars(hostNameC, 0, valueL);
-        } else {
-
-            request.serverName().setChars(hostNameC, 0, colonPos);
-
-            int port = 0;
-            int mult = 1;
-            for (int i = valueL - 1; i > colonPos; i--) {
-                int charValue = HexUtils.getDec(valueB[i + valueS]);
-                if (charValue == -1) {
-                    // Invalid character
-                    // 400 - Bad request
-                    response.setStatus(400);
-                    setErrorState(ErrorState.CLOSE_CLEAN, null);
-                    break;
-                }
-                port = port + (charValue * mult);
-                mult = 10 * mult;
-            }
-            request.setServerPort(port);
-        }
-    }
-
-
-    /**
-     * When committing the response, we have to validate the set of headers, as
-     * well as setup the response filters.
-     */
-    protected void prepareResponse() throws IOException {
-
-        response.setCommitted(true);
-
-        tmpMB.recycle();
-        responseMsgPos = -1;
-        responseMessage.reset();
-        responseMessage.appendByte(Constants.JK_AJP13_SEND_HEADERS);
-
-        // Responses with certain status codes are not permitted to include a
-        // response body.
-        int statusCode = response.getStatus();
-        if (statusCode < 200 || statusCode == 204 || statusCode == 205 ||
-                statusCode == 304) {
-            // No entity body
-            swallowResponse = true;
-        }
-
-        // Responses to HEAD requests are not permitted to include a response
-        // body.
-        MessageBytes methodMB = request.method();
-        if (methodMB.equals("HEAD")) {
-            // No entity body
-            swallowResponse = true;
-        }
-
-        // HTTP header contents
-        responseMessage.appendInt(statusCode);
-        String message = null;
-        if (org.apache.coyote.Constants.USE_CUSTOM_STATUS_MSG_IN_HEADER &&
-                HttpMessages.isSafeInHttpHeader(response.getMessage())) {
-            message = response.getMessage();
-        }
-        if (message == null){
-            message = HttpMessages.getInstance(
-                    response.getLocale()).getMessage(response.getStatus());
-        }
-        if (message == null) {
-            // mod_jk + httpd 2.x fails with a null status message - bug 45026
-            message = Integer.toString(response.getStatus());
-        }
-        tmpMB.setString(message);
-        responseMessage.appendBytes(tmpMB);
-
-        // Special headers
-        MimeHeaders headers = response.getMimeHeaders();
-        String contentType = response.getContentType();
-        if (contentType != null) {
-            headers.setValue("Content-Type").setString(contentType);
-        }
-        String contentLanguage = response.getContentLanguage();
-        if (contentLanguage != null) {
-            headers.setValue("Content-Language").setString(contentLanguage);
-        }
-        long contentLength = response.getContentLengthLong();
-        if (contentLength >= 0) {
-            headers.setValue("Content-Length").setLong(contentLength);
-        }
-
-        // Other headers
-        int numHeaders = headers.size();
-        responseMessage.appendInt(numHeaders);
-        for (int i = 0; i < numHeaders; i++) {
-            MessageBytes hN = headers.getName(i);
-            int hC = Constants.getResponseAjpIndex(hN.toString());
-            if (hC > 0) {
-                responseMessage.appendInt(hC);
-            }
-            else {
-                responseMessage.appendBytes(hN);
-            }
-            MessageBytes hV=headers.getValue(i);
-            responseMessage.appendBytes(hV);
-        }
-
-        // Write to buffer
-        responseMessage.end();
-        output(responseMessage.getBuffer(), 0, responseMessage.getLen(), true);
-    }
-
-
-    /**
-     * Callback to write data from the buffer.
-     */
-    protected void flush(boolean explicit) throws IOException {
-        // Calling code should ensure that there is no data in the buffers for
-        // non-blocking writes.
-        // TODO Validate the assertion above
-        if (explicit && !finished) {
-            // Send the flush message
-            output(flushMessageArray, 0, flushMessageArray.length, true);
-        }
-    }
-
-
-    /**
-     * Finish AJP response.
-     */
-    protected void finish() throws IOException {
-
-        if (!response.isCommitted()) {
-            // Validate and write response headers
-            try {
-                prepareResponse();
-            } catch (IOException e) {
-                setErrorState(ErrorState.CLOSE_NOW, e);
-                return;
-            }
-        }
-
-        if (finished)
-            return;
-
-        finished = true;
-
-        // Swallow the unread body packet if present
-        if (waitingForBodyMessage || first && request.getContentLengthLong() > 0) {
-            refillReadBuffer(true);
-        }
-
-        // Add the end message
-        if (getErrorState().isError()) {
-            output(endAndCloseMessageArray, 0, endAndCloseMessageArray.length, true);
-        } else {
-            output(endMessageArray, 0, endMessageArray.length, true);
-        }
-    }
-
-
-    private int output(byte[] src, int offset, int length,
-            boolean block) throws IOException {
-        if (socketWrapper == null || socketWrapper.getSocket() == null)
-            return -1;
-
-        return socketWrapper.write(block, src, offset, length);
-    }
-
-
-    private boolean available() {
-        if (endOfStream) {
-            return false;
-        }
-        if (empty) {
-            try {
-                refillReadBuffer(false);
-            } catch (IOException timeout) {
-                // Not ideal. This will indicate that data is available
-                // which should trigger a read which in turn will trigger
-                // another IOException and that one can be thrown.
-                return true;
-            }
-        }
-        return !empty;
-    }
-
-
-    /**
-     * Read at least the specified amount of bytes, and place them
-     * in the input buffer. Note that if any data is available to read then this
-     * method will always block until at least the specified number of bytes
-     * have been read.
-     *
-     * @param buf   Buffer to read data into
-     * @param pos   Start position
-     * @param n     The minimum number of bytes to read
-     * @param block If there is no data available to read when this method is
-     *              called, should this call block until data becomes available?
-     * @return  <code>true</code> if the requested number of bytes were read
-     *          else <code>false</code>
-     * @throws IOException
-     */
-    private boolean read(byte[] buf, int pos, int n, boolean block) throws IOException {
-        int read = socketWrapper.read(block, buf, pos, n);
-        if (!block && read > 0 && read < n) {
-            socketWrapper.read(true, buf, pos + n, n - read);
-        }
-
-        return read > 0;
-    }
-
-
-    private void writeData(ByteChunk chunk) throws IOException {
-        // Prevent timeout
-        socketWrapper.access();
-
-        boolean blocking = (response.getWriteListener() == null);
-        if (!blocking) {
-            flushBufferedData();
-        }
-
-        int len = chunk.getLength();
-        int off = 0;
-
-        // Write this chunk
-        while (responseMsgPos == -1 && len > 0) {
-            int thisTime = len;
-            if (thisTime > outputMaxChunkSize) {
-                thisTime = outputMaxChunkSize;
-            }
-            responseMessage.reset();
-            responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
-            responseMessage.appendBytes(chunk.getBytes(), chunk.getOffset() + off, thisTime);
-            responseMessage.end();
-            writeResponseMessage(blocking);
-
-            len -= thisTime;
-            off += thisTime;
-        }
-
-        bytesWritten += off;
-
-        if (len > 0) {
-            // Add this chunk to the buffer
-            addToBuffers(chunk.getBuffer(), off, len);
-        }
-    }
-
-
-    private void addToBuffers(byte[] buf, int offset, int length) {
-        ByteBufferHolder holder = bufferedWrites.peekLast();
-        if (holder == null || holder.isFlipped() || holder.getBuf().remaining() < length) {
-            ByteBuffer buffer = ByteBuffer.allocate(Math.max(bufferedWriteSize,length));
-            holder = new ByteBufferHolder(buffer, false);
-            bufferedWrites.add(holder);
-        }
-        holder.getBuf().put(buf, offset, length);
-    }
-
-
-    private boolean hasDataToWrite() {
-        return responseMsgPos != -1 || bufferedWrites.size() > 0;
-    }
-
-
-    private void flushBufferedData() throws IOException {
-
-        if (responseMsgPos > -1) {
-            // Must be using non-blocking IO
-            // Partially written response message. Try and complete it.
-            writeResponseMessage(false);
-        }
-
-        while (responseMsgPos == -1 && bufferedWrites.size() > 0) {
-            // Try and write any remaining buffer data
-            Iterator<ByteBufferHolder> holders = bufferedWrites.iterator();
-            ByteBufferHolder holder = holders.next();
-            holder.flip();
-            ByteBuffer buffer = holder.getBuf();
-            int initialBufferSize = buffer.remaining();
-            while (responseMsgPos == -1 && buffer.remaining() > 0) {
-                transferToResponseMsg(buffer);
-                writeResponseMessage(false);
-            }
-            bytesWritten += (initialBufferSize - buffer.remaining());
-            if (buffer.remaining() == 0) {
-                holders.remove();
-            }
-        }
-    }
-
-
-    private void transferToResponseMsg(ByteBuffer buffer) {
-
-        int thisTime = buffer.remaining();
-        if (thisTime > outputMaxChunkSize) {
-            thisTime = outputMaxChunkSize;
-        }
-
-        responseMessage.reset();
-        responseMessage.appendByte(Constants.JK_AJP13_SEND_BODY_CHUNK);
-        buffer.get(responseMessage.getBuffer(), responseMessage.pos, thisTime);
-        responseMessage.end();
-    }
-
-
-    private void writeResponseMessage(boolean block) throws IOException {
-        int len = responseMessage.getLen();
-        int written = 1;
-        if (responseMsgPos == -1) {
-            // New message. Advance the write position to the beginning
-            responseMsgPos = 0;
-        }
-
-        while (written > 0 && responseMsgPos < len) {
-            written = output(
-                    responseMessage.getBuffer(), responseMsgPos, len - responseMsgPos, block);
-            responseMsgPos += written;
-        }
-
-        // Message fully written, reset the position for a new message.
-        if (responseMsgPos == len) {
-            responseMsgPos = -1;
-        }
-    }
-
-
-    @Override
-    protected void registerForEvent(boolean read, boolean write) {
-        socketWrapper.regsiterForEvent(read, write);
-    }
-
-
     @Override
     protected Log getLog() {
         return log;
     }
 
 
-    // ------------------------------------- InputStreamInputBuffer Inner Class
+    public AjpProcessor(int packetSize, JIoEndpoint endpoint) {
 
-    /**
-     * This class is an input buffer which will read its data from an input
-     * stream.
-     */
-    protected class SocketInputBuffer implements InputBuffer {
+        super(packetSize, endpoint);
 
-        /**
-         * Read bytes into the specified chunk.
-         */
-        @Override
-        public int doRead(ByteChunk chunk, Request req) throws IOException {
+        response.setOutputBuffer(new SocketOutputBuffer());
+    }
 
-            if (endOfStream) {
-                return -1;
-            }
-            if (empty) {
-                if (!refillReadBuffer(true)) {
-                    return -1;
-                }
-            }
-            ByteChunk bc = bodyBytes.getByteChunk();
-            chunk.setBytes(bc.getBuffer(), bc.getStart(), bc.getLength());
-            empty = true;
-            return chunk.getLength();
+
+    protected InputStream input;
+
+    protected OutputStream output;
+
+
+    @Override
+    public void recycle(boolean socketClosing) {
+        super.recycle(socketClosing);
+        if (socketClosing) {
+            input = null;
+            output = null;
         }
     }
 
 
-    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+    @Override
+    protected void registerForEvent(boolean read, boolean write) {
+        // NO-OP for BIO
+    }
 
-    /**
-     * This class is an output buffer which will write data to an output
-     * stream.
-     */
-    protected class SocketOutputBuffer implements OutputBuffer {
+    @Override
+    protected void resetTimeouts() {
+        // NO-OP. The AJP BIO connector only uses the timeout value on the
+        //        SocketWrapper for async timeouts.
+    }
 
-        /**
-         * Write chunk.
-         */
-        @Override
-        public int doWrite(ByteChunk chunk, Response res) throws IOException {
 
-            if (!response.isCommitted()) {
-                // Validate and write response headers
-                try {
-                    prepareResponse();
-                } catch (IOException e) {
-                    setErrorState(ErrorState.CLOSE_NOW, e);
-                }
+    @Override
+    protected void setupSocket(SocketWrapper<Socket> socketWrapper)
+            throws IOException {
+        input = socketWrapper.getSocket().getInputStream();
+        output = socketWrapper.getSocket().getOutputStream();
+    }
+
+
+    @Override
+    protected void setTimeout(SocketWrapper<Socket> socketWrapper,
+            int timeout) throws IOException {
+        socketWrapper.getSocket().setSoTimeout(timeout);
+    }
+
+
+    @Override
+    protected int output(byte[] src, int offset, int length, boolean block)
+            throws IOException {
+        output.write(src, offset, length);
+        return length;
+    }
+
+
+    @Override
+    protected boolean read(byte[] buf, int pos, int n, boolean blockFirstRead)
+        throws IOException {
+
+        int read = 0;
+        int res = 0;
+        while (read < n) {
+            res = input.read(buf, read + pos, n - read);
+            if (res > 0) {
+                read += res;
+            } else {
+                throw new IOException(sm.getString("ajpprocessor.failedread"));
             }
-
-            if (!swallowResponse) {
-                writeData(chunk);
-            }
-            return chunk.getLength();
         }
 
-        @Override
-        public long getBytesWritten() {
-            return bytesWritten;
-        }
+        return true;
     }
 }
diff --git a/java/org/apache/coyote/ajp/AjpProtocol.java b/java/org/apache/coyote/ajp/AjpProtocol.java
new file mode 100644
index 0000000..8cf946b
--- /dev/null
+++ b/java/org/apache/coyote/ajp/AjpProtocol.java
@@ -0,0 +1,144 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.ajp;
+
+import java.net.Socket;
+
+import org.apache.coyote.AbstractProtocol;
+import org.apache.coyote.Processor;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class AjpProtocol extends AbstractAjpProtocol<Socket> {
+
+
+    private static final Log log = LogFactory.getLog(AjpProtocol.class);
+
+    @Override
+    protected Log getLog() { return log; }
+
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    public AjpProtocol() {
+        endpoint = new JIoEndpoint();
+        cHandler = new AjpConnectionHandler(this);
+        ((JIoEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+
+    /**
+     * Connection handler for AJP.
+     */
+    private final AjpConnectionHandler cHandler;
+
+
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("ajp-bio");
+    }
+
+
+    // --------------------------------------  AjpConnectionHandler Inner Class
+
+
+    protected static class AjpConnectionHandler
+            extends AbstractAjpConnectionHandler<Socket,AjpProcessor>
+            implements Handler {
+
+        protected final AjpProtocol proto;
+
+        public AjpConnectionHandler(AjpProtocol proto) {
+            this.proto = proto;
+        }
+
+        @Override
+        protected AbstractProtocol<Socket> getProtocol() {
+            return proto;
+        }
+
+        @Override
+        protected Log getLog() {
+            return log;
+        }
+
+        @Override
+        public SSLImplementation getSslImplementation() {
+            // AJP does not support SSL
+            return null;
+        }
+
+        /**
+         * Expected to be used by the handler once the processor is no longer
+         * required.
+         *
+         * @param socket            Ignored for BIO
+         * @param processor
+         * @param isSocketClosing
+         * @param addToPoller       Ignored for BIO
+         */
+        @Override
+        public void release(SocketWrapper<Socket> socket,
+                Processor<Socket> processor, boolean isSocketClosing,
+                boolean addToPoller) {
+            processor.recycle(isSocketClosing);
+            recycledProcessors.push(processor);
+        }
+
+
+        @Override
+        protected AjpProcessor createProcessor() {
+            AjpProcessor processor = new AjpProcessor(proto.packetSize, (JIoEndpoint)proto.endpoint);
+            proto.configureProcessor(processor);
+            register(processor);
+            return processor;
+        }
+
+        @Override
+        public void beforeHandshake(SocketWrapper<Socket> socket) {
+        }
+    }
+}
diff --git a/java/org/apache/coyote/ajp/Constants.java b/java/org/apache/coyote/ajp/Constants.java
index bf12f80..7a6e859 100644
--- a/java/org/apache/coyote/ajp/Constants.java
+++ b/java/org/apache/coyote/ajp/Constants.java
@@ -25,7 +25,14 @@
  */
 public final class Constants {
 
+    /**
+     * Package name.
+     */
+    public static final String Package = "org.apache.coyote.ajp";
+
+    public static final int DEFAULT_CONNECTION_LINGER = -1;
     public static final int DEFAULT_CONNECTION_TIMEOUT = -1;
+    public static final boolean DEFAULT_TCP_NO_DELAY = true;
 
     // Prefix codes for message types from server to container
     public static final byte JK_AJP13_FORWARD_REQUEST   = 2;
diff --git a/java/org/apache/coyote/ajp/LocalStrings.properties b/java/org/apache/coyote/ajp/LocalStrings.properties
index d160334..fc7b387 100644
--- a/java/org/apache/coyote/ajp/LocalStrings.properties
+++ b/java/org/apache/coyote/ajp/LocalStrings.properties
@@ -15,6 +15,7 @@
 ajpnioprotocol.releaseStart=Iterating through our connections to release a socket channel [{0}]
 ajpnioprotocol.releaseEnd=Done iterating through our connections to release a socket channel [{0}] released [{1}]
 
+ajpprocessor.comet.notsupported=Comet is not supported by the AJP protocol
 ajpprocessor.failedread=Socket read failed
 ajpprocessor.failedsend=Failed to send AJP message
 ajpprocessor.header.error=Header message parsing failed
@@ -23,6 +24,7 @@
 ajpprocessor.request.prepare=Error preparing request
 ajpprocessor.request.process=Error processing request
 ajpprocessor.certs.fail=Certificate conversion failed
+ajpprocessor.comet.notsupported=The Comet protocol is not supported by this connector
 ajpprocessor.ssl.notsupported=The SSL protocol is not supported by this connector
 ajpprocessor.httpupgrade.notsupported=HTTP upgrade is not supported by the AJP protocol
 
diff --git a/java/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java b/java/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java
index b513aa0..e2f4ac1 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11JsseProtocol.java
@@ -16,7 +16,6 @@
  */
 package org.apache.coyote.http11;
 
-import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SSLImplementation;
 
 public abstract class AbstractHttp11JsseProtocol<S>
@@ -24,89 +23,85 @@
 
     protected SSLImplementation sslImplementation = null;
 
-    public AbstractHttp11JsseProtocol(AbstractEndpoint<S> endpoint) {
-        super(endpoint);
-    }
+    public String getAlgorithm() { return endpoint.getAlgorithm();}
+    public void setAlgorithm(String s ) { endpoint.setAlgorithm(s);}
 
-    public String getAlgorithm() { return getEndpoint().getAlgorithm();}
-    public void setAlgorithm(String s ) { getEndpoint().setAlgorithm(s);}
+    public String getClientAuth() { return endpoint.getClientAuth();}
+    public void setClientAuth(String s ) { endpoint.setClientAuth(s);}
 
-    public String getClientAuth() { return getEndpoint().getClientAuth();}
-    public void setClientAuth(String s ) { getEndpoint().setClientAuth(s);}
+    public String getKeystoreFile() { return endpoint.getKeystoreFile();}
+    public void setKeystoreFile(String s ) { endpoint.setKeystoreFile(s);}
 
-    public String getKeystoreFile() { return getEndpoint().getKeystoreFile();}
-    public void setKeystoreFile(String s ) { getEndpoint().setKeystoreFile(s);}
+    public String getKeystorePass() { return endpoint.getKeystorePass();}
+    public void setKeystorePass(String s ) { endpoint.setKeystorePass(s);}
 
-    public String getKeystorePass() { return getEndpoint().getKeystorePass();}
-    public void setKeystorePass(String s ) { getEndpoint().setKeystorePass(s);}
-
-    public String getKeystoreType() { return getEndpoint().getKeystoreType();}
-    public void setKeystoreType(String s ) { getEndpoint().setKeystoreType(s);}
+    public String getKeystoreType() { return endpoint.getKeystoreType();}
+    public void setKeystoreType(String s ) { endpoint.setKeystoreType(s);}
 
     public String getKeystoreProvider() {
-        return getEndpoint().getKeystoreProvider();
+        return endpoint.getKeystoreProvider();
     }
     public void setKeystoreProvider(String s ) {
-        getEndpoint().setKeystoreProvider(s);
+        endpoint.setKeystoreProvider(s);
     }
 
-    public String getSslProtocol() { return getEndpoint().getSslProtocol();}
-    public void setSslProtocol(String s) { getEndpoint().setSslProtocol(s);}
+    public String getSslProtocol() { return endpoint.getSslProtocol();}
+    public void setSslProtocol(String s) { endpoint.setSslProtocol(s);}
 
-    public String getCiphers() { return getEndpoint().getCiphers();}
-    public void setCiphers(String s) { getEndpoint().setCiphers(s);}
-    public String[] getCiphersUsed() { return getEndpoint().getCiphersUsed();}
+    public String getCiphers() { return endpoint.getCiphers();}
+    public void setCiphers(String s) { endpoint.setCiphers(s);}
+    public String[] getCiphersUsed() { return endpoint.getCiphersUsed();}
 
-    public String getKeyAlias() { return getEndpoint().getKeyAlias();}
-    public void setKeyAlias(String s ) { getEndpoint().setKeyAlias(s);}
+    public String getKeyAlias() { return endpoint.getKeyAlias();}
+    public void setKeyAlias(String s ) { endpoint.setKeyAlias(s);}
 
-    public String getKeyPass() { return getEndpoint().getKeyPass();}
-    public void setKeyPass(String s ) { getEndpoint().setKeyPass(s);}
+    public String getKeyPass() { return endpoint.getKeyPass();}
+    public void setKeyPass(String s ) { endpoint.setKeyPass(s);}
 
-    public void setTruststoreFile(String f){ getEndpoint().setTruststoreFile(f);}
-    public String getTruststoreFile(){ return getEndpoint().getTruststoreFile();}
+    public void setTruststoreFile(String f){ endpoint.setTruststoreFile(f);}
+    public String getTruststoreFile(){ return endpoint.getTruststoreFile();}
 
-    public void setTruststorePass(String p){ getEndpoint().setTruststorePass(p);}
-    public String getTruststorePass(){return getEndpoint().getTruststorePass();}
+    public void setTruststorePass(String p){ endpoint.setTruststorePass(p);}
+    public String getTruststorePass(){return endpoint.getTruststorePass();}
 
-    public void setTruststoreType(String t){ getEndpoint().setTruststoreType(t);}
-    public String getTruststoreType(){ return getEndpoint().getTruststoreType();}
+    public void setTruststoreType(String t){ endpoint.setTruststoreType(t);}
+    public String getTruststoreType(){ return endpoint.getTruststoreType();}
 
     public void setTruststoreProvider(String t){
-        getEndpoint().setTruststoreProvider(t);
+        endpoint.setTruststoreProvider(t);
     }
     public String getTruststoreProvider(){
-        return getEndpoint().getTruststoreProvider();
+        return endpoint.getTruststoreProvider();
     }
 
     public void setTruststoreAlgorithm(String a){
-        getEndpoint().setTruststoreAlgorithm(a);
+        endpoint.setTruststoreAlgorithm(a);
     }
     public String getTruststoreAlgorithm(){
-        return getEndpoint().getTruststoreAlgorithm();
+        return endpoint.getTruststoreAlgorithm();
     }
 
     public void setTrustMaxCertLength(String s){
-        getEndpoint().setTrustMaxCertLength(s);
+        endpoint.setTrustMaxCertLength(s);
     }
     public String getTrustMaxCertLength(){
-        return getEndpoint().getTrustMaxCertLength();
+        return endpoint.getTrustMaxCertLength();
     }
 
-    public void setCrlFile(String s){getEndpoint().setCrlFile(s);}
-    public String getCrlFile(){ return getEndpoint().getCrlFile();}
+    public void setCrlFile(String s){endpoint.setCrlFile(s);}
+    public String getCrlFile(){ return endpoint.getCrlFile();}
 
-    public void setSessionCacheSize(String s){getEndpoint().setSessionCacheSize(s);}
-    public String getSessionCacheSize(){ return getEndpoint().getSessionCacheSize();}
+    public void setSessionCacheSize(String s){endpoint.setSessionCacheSize(s);}
+    public String getSessionCacheSize(){ return endpoint.getSessionCacheSize();}
 
-    public void setSessionTimeout(String s){getEndpoint().setSessionTimeout(s);}
-    public String getSessionTimeout(){ return getEndpoint().getSessionTimeout();}
+    public void setSessionTimeout(String s){endpoint.setSessionTimeout(s);}
+    public String getSessionTimeout(){ return endpoint.getSessionTimeout();}
 
     public void setAllowUnsafeLegacyRenegotiation(String s) {
-        getEndpoint().setAllowUnsafeLegacyRenegotiation(s);
+        endpoint.setAllowUnsafeLegacyRenegotiation(s);
     }
     public String getAllowUnsafeLegacyRenegotiation() {
-        return getEndpoint().getAllowUnsafeLegacyRenegotiation();
+        return endpoint.getAllowUnsafeLegacyRenegotiation();
     }
 
     private String sslImplementationName = null;
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Processor.java b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
index b51a78d..daa8534 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Processor.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Processor.java
@@ -54,7 +54,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.DispatchType;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
 public abstract class AbstractHttp11Processor<S> extends AbstractProcessor<S> {
@@ -72,7 +72,7 @@
     /**
      * Input.
      */
-    protected AbstractInputBuffer<S> inputBuffer;
+    protected AbstractInputBuffer<S> inputBuffer ;
 
 
     /**
@@ -150,6 +150,12 @@
 
 
     /**
+     * Comet used.
+     */
+    protected boolean comet = false;
+
+
+    /**
      * Regular expression that defines the restricted user agents.
      */
     protected Pattern restrictedUserAgents = null;
@@ -457,6 +463,20 @@
     }
 
     /**
+     * Set the socket buffer flag.
+     */
+    public void setSocketBuffer(int socketBuffer) {
+        outputBuffer.setSocketBuffer(socketBuffer);
+    }
+
+    /**
+     * Get the socket buffer flag.
+     */
+    public int getSocketBuffer() {
+        return outputBuffer.getSocketBuffer();
+    }
+
+    /**
      * Set the upload timeout.
      */
     public void setConnectionUploadTimeout(int timeout) {
@@ -615,18 +635,14 @@
      * Exposes input buffer to super class to allow better code re-use.
      * @return  The input buffer used by the processor.
      */
-    protected AbstractInputBuffer<S> getInputBuffer() {
-        return inputBuffer;
-    }
+    protected abstract AbstractInputBuffer<S> getInputBuffer();
 
 
     /**
      * Exposes output buffer to super class to allow better code re-use.
      * @return  The output buffer used by the processor.
      */
-    protected AbstractOutputBuffer<S> getOutputBuffer() {
-        return outputBuffer;
-    }
+    protected abstract AbstractOutputBuffer<S> getOutputBuffer();
 
 
     /**
@@ -782,6 +798,8 @@
         }
         case ASYNC_START: {
             asyncStateMachine.asyncStart((AsyncContextCallback) param);
+            // Async time out is based on SocketWrapper access time
+            getSocketWrapper().access();
             break;
         }
         case ASYNC_DISPATCHED: {
@@ -836,8 +854,9 @@
             if (param == null || socketWrapper == null) {
                 return;
             }
-            long timeout = ((Long) param).longValue();
-            socketWrapper.setAsyncTimeout(timeout);
+            long timeout = ((Long)param).longValue();
+            // If we are not piggy backing on a worker thread, set the timeout
+            socketWrapper.setTimeout(timeout);
             break;
         }
         case ASYNC_DISPATCH: {
@@ -853,7 +872,7 @@
             break;
         }
         case AVAILABLE: {
-            request.setAvailable(getInputBuffer().available());
+            request.setAvailable(inputBuffer.available());
             break;
         }
         case NB_WRITE_INTEREST: {
@@ -884,7 +903,7 @@
             break;
         }
         case DISPATCH_EXECUTE: {
-            SocketWrapperBase<S> wrapper = socketWrapper;
+            SocketWrapper<S> wrapper = socketWrapper;
             if (wrapper != null) {
                 getEndpoint().executeNonBlockingDispatches(wrapper);
             }
@@ -907,6 +926,20 @@
 
 
     /**
+     * Processors (currently only HTTP BIO) may elect to disable HTTP keep-alive
+     * in some circumstances. This method allows the processor implementation to
+     * determine if keep-alive should be disabled or not.
+     */
+    protected abstract boolean disableKeepAlive();
+
+
+    /**
+     * Configures the timeout to be used for reading the request line.
+     */
+    protected abstract void setRequestLineReadTimeout() throws IOException;
+
+
+    /**
      * Defines how a connector handles an incomplete request line read.
      *
      * @return <code>true</code> if the processor should break out of the
@@ -931,7 +964,7 @@
      * @throws IOException error during an I/O operation
      */
     @Override
-    public SocketState process(SocketWrapperBase<S> socketWrapper)
+    public SocketState process(SocketWrapper<S> socketWrapper)
         throws IOException {
         RequestInfo rp = request.getRequestProcessor();
         rp.setStage(org.apache.coyote.Constants.STAGE_PARSE);
@@ -943,16 +976,27 @@
 
         // Flags
         keepAlive = true;
+        comet = false;
         openSocket = false;
         sendfileInProgress = false;
         readComplete = true;
-        keptAlive = false;
+        if (endpoint.getUsePolling()) {
+            keptAlive = false;
+        } else {
+            keptAlive = socketWrapper.isKeptAlive();
+        }
 
-        while (!getErrorState().isError() && keepAlive && !isAsync() &&
+        if (disableKeepAlive()) {
+            socketWrapper.setKeepAliveLeft(0);
+        }
+
+        while (!getErrorState().isError() && keepAlive && !comet && !isAsync() &&
                 httpUpgradeHandler == null && !endpoint.isPaused()) {
 
             // Parsing the request header
             try {
+                setRequestLineReadTimeout();
+
                 if (!getInputBuffer().parseRequestLine(keptAlive)) {
                     if (handleIncompleteRequestLineRead()) {
                         break;
@@ -1051,6 +1095,7 @@
                                     statusDropsConnection(response.getStatus())))) {
                         setErrorState(ErrorState.CLOSE_CLEAN, null);
                     }
+                    setCometTimeouts(socketWrapper);
                 } catch (InterruptedIOException e) {
                     setErrorState(ErrorState.CLOSE_NOW, e);
                 } catch (HeadersTooLargeException e) {
@@ -1078,7 +1123,7 @@
             // Finish the handling of the request
             rp.setStage(org.apache.coyote.Constants.STAGE_ENDINPUT);
 
-            if (!isAsync()) {
+            if (!isAsync() && !comet) {
                 if (getErrorState().isError()) {
                     // If we know we are closing the connection, don't drain
                     // input. This way uploading a 100GB file doesn't tie up the
@@ -1102,7 +1147,7 @@
             }
             request.updateCounters();
 
-            if (!isAsync() || getErrorState().isError()) {
+            if (!isAsync() && !comet || getErrorState().isError()) {
                 if (getErrorState().isIoAllowed()) {
                     getInputBuffer().nextRequest();
                     getOutputBuffer().nextRequest();
@@ -1128,7 +1173,7 @@
 
         if (getErrorState().isError() || endpoint.isPaused()) {
             return SocketState.CLOSED;
-        } else if (isAsync()) {
+        } else if (isAsync() || comet) {
             return SocketState.LONG;
         } else if (isUpgrade()) {
             return SocketState.UPGRADING;
@@ -1632,7 +1677,7 @@
         } else if (status == SocketStatus.OPEN_READ &&
                 request.getReadListener() != null) {
             try {
-                if (getInputBuffer().available() > 0) {
+                if (inputBuffer.available() > 0) {
                     asyncStateMachine.asyncOperation();
                 }
             } catch (IllegalStateException x) {
@@ -1679,6 +1724,12 @@
 
 
     @Override
+    public boolean isComet() {
+        return comet;
+    }
+
+
+    @Override
     public boolean isUpgrade() {
         return httpUpgradeHandler != null;
     }
@@ -1707,6 +1758,12 @@
     protected abstract void resetTimeouts();
 
 
+    /**
+     * Provides a mechanism for those connectors (currently only NIO) that need
+     * that need to set comet timeouts.
+     */
+    protected abstract void setCometTimeouts(SocketWrapper<S> socketWrapper);
+
     public void endRequest() {
 
         // Finish the handling of the request
@@ -1746,7 +1803,7 @@
      * @return true if the keep-alive loop should be broken
      */
     protected abstract boolean breakKeepAliveLoop(
-            SocketWrapperBase<S> socketWrapper);
+            SocketWrapper<S> socketWrapper);
 
 
     @Override
@@ -1763,8 +1820,8 @@
             asyncStateMachine.recycle();
         }
         httpUpgradeHandler = null;
+        comet = false;
         resetErrorState();
-        socketWrapper = null;
         recycleInternal();
     }
 
@@ -1773,7 +1830,7 @@
 
     @Override
     public ByteBuffer getLeftoverInput() {
-        return getInputBuffer().getLeftover();
+        return inputBuffer.getLeftover();
     }
 
 }
diff --git a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
index feee770..866472e 100644
--- a/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
+++ b/java/org/apache/coyote/http11/AbstractHttp11Protocol.java
@@ -16,17 +16,11 @@
  */
 package org.apache.coyote.http11;
 
+
 import org.apache.coyote.AbstractProtocol;
-import org.apache.tomcat.util.net.AbstractEndpoint;
 
 public abstract class AbstractHttp11Protocol<S> extends AbstractProtocol<S> {
 
-    public AbstractHttp11Protocol(AbstractEndpoint<S> endpoint) {
-        super(endpoint);
-        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
-    }
-
-
     @Override
     protected String getProtocolName() {
         return "Http";
@@ -36,6 +30,13 @@
     // ------------------------------------------------ HTTP specific properties
     // ------------------------------------------ managed in the ProtocolHandler
 
+    private int socketBuffer = 9000;
+    public int getSocketBuffer() { return socketBuffer; }
+    public void setSocketBuffer(int socketBuffer) {
+        this.socketBuffer = socketBuffer;
+    }
+
+
     /**
      * Maximum size of the post which will be saved when processing certain
      * requests, such as a POST.
@@ -189,9 +190,9 @@
     // ------------------------------------------------ HTTP specific properties
     // ------------------------------------------ passed through to the EndPoint
 
-    public boolean isSSLEnabled() { return getEndpoint().isSSLEnabled();}
+    public boolean isSSLEnabled() { return endpoint.isSSLEnabled();}
     public void setSSLEnabled(boolean SSLEnabled) {
-        getEndpoint().setSSLEnabled(SSLEnabled);
+        endpoint.setSSLEnabled(SSLEnabled);
     }
 
 
@@ -200,10 +201,10 @@
      * connection. The default is the same as for Apache HTTP Server.
      */
     public int getMaxKeepAliveRequests() {
-        return getEndpoint().getMaxKeepAliveRequests();
+        return endpoint.getMaxKeepAliveRequests();
     }
     public void setMaxKeepAliveRequests(int mkar) {
-        getEndpoint().setMaxKeepAliveRequests(mkar);
+        endpoint.setMaxKeepAliveRequests(mkar);
     }
 
     protected NpnHandler<S> npnHandler;
@@ -232,6 +233,7 @@
         processor.setNoCompressionUserAgents(getNoCompressionUserAgents());
         processor.setCompressableMimeTypes(getCompressableMimeTypes());
         processor.setRestrictedUserAgents(getRestrictedUserAgents());
+        processor.setSocketBuffer(getSocketBuffer());
         processor.setMaxSavePostSize(getMaxSavePostSize());
         processor.setServer(getServer());
     }
diff --git a/java/org/apache/coyote/http11/AbstractInputBuffer.java b/java/org/apache/coyote/http11/AbstractInputBuffer.java
index bfdec61..92ffd1f 100644
--- a/java/org/apache/coyote/http11/AbstractInputBuffer.java
+++ b/java/org/apache/coyote/http11/AbstractInputBuffer.java
@@ -25,7 +25,7 @@
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.http.MimeHeaders;
 import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
 public abstract class AbstractInputBuffer<S> implements InputBuffer{
@@ -246,7 +246,7 @@
      */
     protected abstract boolean fill(boolean block) throws IOException;
 
-    protected abstract void init(SocketWrapperBase<S> socketWrapper,
+    protected abstract void init(SocketWrapper<S> socketWrapper,
             AbstractEndpoint<S> endpoint) throws IOException;
 
     protected abstract Log getLog();
diff --git a/java/org/apache/coyote/http11/AbstractOutputBuffer.java b/java/org/apache/coyote/http11/AbstractOutputBuffer.java
index 8e85bca..401174b 100644
--- a/java/org/apache/coyote/http11/AbstractOutputBuffer.java
+++ b/java/org/apache/coyote/http11/AbstractOutputBuffer.java
@@ -17,7 +17,6 @@
 package org.apache.coyote.http11;
 
 import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 import java.util.Iterator;
@@ -28,13 +27,11 @@
 import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Response;
 import org.apache.coyote.http11.filters.GzipOutputFilter;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.http.HttpMessages;
 import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
 public abstract class AbstractOutputBuffer<S> implements OutputBuffer {
@@ -100,8 +97,10 @@
      */
     protected long byteCount = 0;
 
-    protected ByteBuffer socketWriteBuffer;
-    protected volatile boolean writeBufferFlipped;
+    /**
+     * Socket buffering.
+     */
+    protected int socketBuffer = -1;
 
     /**
      * For "non-blocking" writes use an external set of buffers. Although the
@@ -131,8 +130,6 @@
         committed = false;
         finished = false;
 
-        outputStreamOutputBuffer = new SocketOutputBuffer();
-
         // Cause loading of HttpMessages
         HttpMessages.getInstance(response.getLocale()).getMessage(200);
     }
@@ -149,7 +146,8 @@
     /**
      * Logger.
      */
-    private static final Log log = LogFactory.getLog(AbstractOutputBuffer.class);
+    private static final org.apache.juli.logging.Log log
+        = org.apache.juli.logging.LogFactory.getLog(AbstractOutputBuffer.class);
 
     // ------------------------------------------------------------- Properties
 
@@ -204,6 +202,22 @@
     }
 
 
+    /**
+     * Set the socket buffer flag.
+     */
+    public void setSocketBuffer(int socketBuffer) {
+        this.socketBuffer = socketBuffer;
+    }
+
+
+    /**
+     * Get the socket buffer flag.
+     */
+    public int getSocketBuffer() {
+        return socketBuffer;
+    }
+
+
     public void setBufferedWriteSize(int bufferedWriteSize) {
         this.bufferedWriteSize = bufferedWriteSize;
     }
@@ -317,7 +331,6 @@
         // Sub-classes may wish to do more than this.
         nextRequest();
         bufferedWrites.clear();
-        writeBufferFlipped = false;
     }
 
     /**
@@ -368,26 +381,12 @@
     }
 
 
-    public abstract void init(SocketWrapperBase<S> socketWrapper,
+    public abstract void init(SocketWrapper<S> socketWrapper,
             AbstractEndpoint<S> endpoint) throws IOException;
 
     public abstract void sendAck() throws IOException;
 
-    /**
-     * Commit the response.
-     *
-     * @throws IOException an underlying I/O error occurred
-     */
-    protected void commit() throws IOException {
-        // The response is now committed
-        committed = true;
-        response.setCommitted(true);
-
-        if (pos > 0) {
-            // Sending the response header buffer
-            addToBB(headerBuffer, 0, pos);
-        }
-    }
+    protected abstract void commit() throws IOException;
 
 
     /**
@@ -602,20 +601,12 @@
     }
 
 
-    protected abstract void addToBB(byte[] buf, int offset, int length) throws IOException;
-
-
     //------------------------------------------------------ Non-blocking writes
 
+    protected abstract boolean hasMoreDataToFlush();
     protected abstract void registerWriteInterest() throws IOException;
 
 
-    protected boolean hasMoreDataToFlush() {
-        return (writeBufferFlipped && socketWriteBuffer.remaining() > 0) ||
-        (!writeBufferFlipped && socketWriteBuffer.position() > 0);
-    }
-
-
     /**
      * Writes any remaining buffered data.
      *
@@ -659,50 +650,4 @@
         }
         return result;
     }
-
-
-    // --------------------------------------------------------- Utility methods
-
-    protected static int transfer(byte[] from, int offset, int length, ByteBuffer to) {
-        int max = Math.min(length, to.remaining());
-        to.put(from, offset, max);
-        return max;
-    }
-
-
-    protected static void transfer(ByteBuffer from, ByteBuffer to) {
-        int max = Math.min(from.remaining(), to.remaining());
-        int fromLimit = from.limit();
-        from.limit(from.position() + max);
-        to.put(from);
-        from.limit(fromLimit);
-    }
-
-
-
-    // ------------------------------------------ SocketOutputBuffer Inner Class
-
-    /**
-     * This class is an output buffer which will write data to an output stream.
-     */
-    protected class SocketOutputBuffer implements OutputBuffer {
-
-        /**
-         * Write chunk.
-         */
-        @Override
-        public int doWrite(ByteChunk chunk, Response res) throws IOException {
-            int len = chunk.getLength();
-            int start = chunk.getStart();
-            byte[] b = chunk.getBuffer();
-            addToBB(b, start, len);
-            byteCount += len;
-            return len;
-        }
-
-        @Override
-        public long getBytesWritten() {
-            return byteCount;
-        }
-    }
 }
diff --git a/java/org/apache/coyote/http11/Constants.java b/java/org/apache/coyote/http11/Constants.java
index 33520c1..15e26ea 100644
--- a/java/org/apache/coyote/http11/Constants.java
+++ b/java/org/apache/coyote/http11/Constants.java
@@ -36,7 +36,9 @@
      */
     public static final String Package = "org.apache.coyote.http11";
 
+    public static final int DEFAULT_CONNECTION_LINGER = -1;
     public static final int DEFAULT_CONNECTION_TIMEOUT = 60000;
+    public static final boolean DEFAULT_TCP_NO_DELAY = true;
 
 
     /**
diff --git a/java/org/apache/coyote/http11/Http11AprProcessor.java b/java/org/apache/coyote/http11/Http11AprProcessor.java
index df73286..e4ecd1a 100644
--- a/java/org/apache/coyote/http11/Http11AprProcessor.java
+++ b/java/org/apache/coyote/http11/Http11AprProcessor.java
@@ -17,11 +17,14 @@
 package org.apache.coyote.http11;
 
 import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.ErrorState;
+import org.apache.coyote.RequestInfo;
 import org.apache.coyote.http11.filters.BufferedInputFilter;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -30,9 +33,12 @@
 import org.apache.tomcat.jni.SSLSocket;
 import org.apache.tomcat.jni.Sockaddr;
 import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.AprEndpoint;
 import org.apache.tomcat.util.net.SSLSupport;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -58,10 +64,10 @@
         super(endpoint);
 
         inputBuffer = new InternalAprInputBuffer(request, headerBufferSize);
-        request.setInputBuffer(getInputBuffer());
+        request.setInputBuffer(inputBuffer);
 
         outputBuffer = new InternalAprOutputBuffer(response, headerBufferSize);
-        response.setOutputBuffer(getOutputBuffer());
+        response.setOutputBuffer(outputBuffer);
 
         initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
     }
@@ -92,6 +98,77 @@
     // --------------------------------------------------------- Public Methods
 
 
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    @Override
+    public SocketState event(SocketStatus status)
+        throws IOException {
+
+        RequestInfo rp = request.getRequestProcessor();
+
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            if (!getAdapter().event(request, response, status)) {
+                setErrorState(ErrorState.CLOSE_NOW, null);
+            }
+        } catch (InterruptedIOException e) {
+            setErrorState(ErrorState.CLOSE_NOW, e);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            setErrorState(ErrorState.CLOSE_NOW, t);
+            getAdapter().log(request, response, 0);
+            log.error(sm.getString("http11processor.request.process"), t);
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (getErrorState().isError() || status==SocketStatus.STOP) {
+            return SocketState.CLOSED;
+        } else if (!comet) {
+            inputBuffer.nextRequest();
+            outputBuffer.nextRequest();
+            return SocketState.OPEN;
+        } else {
+            return SocketState.LONG;
+        }
+    }
+
+    @Override
+    protected boolean disableKeepAlive() {
+        return false;
+    }
+
+
+    @Override
+    protected void setRequestLineReadTimeout() throws IOException {
+        // Timeouts while in the poller are handled entirely by the poller
+        // Only need to be concerned with socket timeouts
+
+        // APR uses simulated blocking so if some request line data is present
+        // then it must all be presented (with the normal socket timeout).
+
+        // When entering the processing loop for the first time there will
+        // always be some data to read so the keep-alive timeout is not required
+
+        // For the second and subsequent executions of the processing loop, if
+        // there is no request line data present then no further data will be
+        // read from the socket. If there is request line data present then it
+        // must all be presented (with the normal socket timeout)
+
+        // When the socket is created it is given the correct timeout.
+        // sendfile may change the timeout but will restore it
+        // This processor may change the timeout for uploads but will restore it
+
+        // NO-OP
+    }
+
+
     @Override
     protected boolean handleIncompleteRequestLineRead() {
         // This means that no data is available right now
@@ -109,7 +186,13 @@
 
 
     @Override
-    protected boolean breakKeepAliveLoop(SocketWrapperBase<Long> socketWrapper) {
+    protected void setCometTimeouts(SocketWrapper<Long> socketWrapper) {
+        // NO-OP for APR/native
+    }
+
+
+    @Override
+    protected boolean breakKeepAliveLoop(SocketWrapper<Long> socketWrapper) {
         openSocket = keepAlive;
         // Do sendfile as needed: add socket to sendfile and end
         if (sendfileData != null && !getErrorState().isError()) {
@@ -152,6 +235,7 @@
 
     @Override
     public void recycleInternal() {
+        socketWrapper = null;
         sendfileData = null;
     }
 
@@ -337,9 +421,9 @@
             if (endpoint.isSSLEnabled() && (socketRef != 0)) {
                 // Consume and buffer the request body, so that it does not
                 // interfere with the client's handshake messages
-                InputFilter[] inputFilters = getInputBuffer().getFilters();
+                InputFilter[] inputFilters = inputBuffer.getFilters();
                 ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER]).setLimit(maxSavePostSize);
-                getInputBuffer().addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
+                inputBuffer.addActiveFilter(inputFilters[Constants.BUFFERED_FILTER]);
                 try {
                     // Configure connection to require a certificate
                     SSLSocket.setVerify(socketRef, SSL.SSL_CVERIFY_REQUIRE,
@@ -371,6 +455,23 @@
             }
             break;
         }
+        case COMET_BEGIN: {
+            comet = true;
+            break;
+        }
+        case COMET_END: {
+            comet = false;
+            break;
+        }
+        case COMET_CLOSE: {
+            ((AprEndpoint)endpoint).processSocket(this.socketWrapper,
+                    SocketStatus.OPEN_READ, true);
+            break;
+        }
+        case COMET_SETTIMEOUT: {
+            //no op
+            break;
+        }
         }
     }
 
@@ -389,7 +490,7 @@
                 org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
         if (fileName != null) {
             // No entity body sent here
-            getOutputBuffer().addActiveFilter(outputFilters[Constants.VOID_FILTER]);
+            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
             contentDelimitation = true;
             sendfileData = new AprEndpoint.SendfileData();
             sendfileData.fileName = fileName;
@@ -401,4 +502,14 @@
         }
         return false;
     }
+
+    @Override
+    protected AbstractInputBuffer<Long> getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer<Long> getOutputBuffer() {
+        return outputBuffer;
+    }
 }
diff --git a/java/org/apache/coyote/http11/Http11AprProtocol.java b/java/org/apache/coyote/http11/Http11AprProtocol.java
index 3af66dc..e737237 100644
--- a/java/org/apache/coyote/http11/Http11AprProtocol.java
+++ b/java/org/apache/coyote/http11/Http11AprProtocol.java
@@ -23,13 +23,15 @@
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeProcessor;
+import org.apache.coyote.http11.upgrade.AprProcessor;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.AprEndpoint.Handler;
 import org.apache.tomcat.util.net.AprEndpoint.Poller;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -44,16 +46,15 @@
 
     private static final Log log = LogFactory.getLog(Http11AprProtocol.class);
 
-    public Http11AprProtocol() {
-        super(new AprEndpoint());
-        Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
-        setHandler(cHandler);
-        ((AprEndpoint) getEndpoint()).setHandler(cHandler);
-    }
+    @Override
+    protected Log getLog() { return log; }
 
 
     @Override
-    protected Log getLog() { return log; }
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
 
     @Override
     public boolean isAprRequired() {
@@ -62,47 +63,59 @@
         return true;
     }
 
-    public boolean getUseSendfile() { return getEndpoint().getUseSendfile(); }
-    public void setUseSendfile(boolean useSendfile) { ((AprEndpoint)getEndpoint()).setUseSendfile(useSendfile); }
 
-    public int getPollTime() { return ((AprEndpoint)getEndpoint()).getPollTime(); }
-    public void setPollTime(int pollTime) { ((AprEndpoint)getEndpoint()).setPollTime(pollTime); }
+    public Http11AprProtocol() {
+        endpoint = new AprEndpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((AprEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+    }
 
-    public void setPollerSize(int pollerSize) { getEndpoint().setMaxConnections(pollerSize); }
-    public int getPollerSize() { return getEndpoint().getMaxConnections(); }
+    private final Http11ConnectionHandler cHandler;
 
-    public int getSendfileSize() { return ((AprEndpoint)getEndpoint()).getSendfileSize(); }
-    public void setSendfileSize(int sendfileSize) { ((AprEndpoint)getEndpoint()).setSendfileSize(sendfileSize); }
+    public boolean getUseSendfile() { return endpoint.getUseSendfile(); }
+    public void setUseSendfile(boolean useSendfile) { ((AprEndpoint)endpoint).setUseSendfile(useSendfile); }
 
-    public void setSendfileThreadCount(int sendfileThreadCount) { ((AprEndpoint)getEndpoint()).setSendfileThreadCount(sendfileThreadCount); }
-    public int getSendfileThreadCount() { return ((AprEndpoint)getEndpoint()).getSendfileThreadCount(); }
+    public int getPollTime() { return ((AprEndpoint)endpoint).getPollTime(); }
+    public void setPollTime(int pollTime) { ((AprEndpoint)endpoint).setPollTime(pollTime); }
 
-    public boolean getDeferAccept() { return ((AprEndpoint)getEndpoint()).getDeferAccept(); }
-    public void setDeferAccept(boolean deferAccept) { ((AprEndpoint)getEndpoint()).setDeferAccept(deferAccept); }
+    public void setPollerSize(int pollerSize) { endpoint.setMaxConnections(pollerSize); }
+    public int getPollerSize() { return endpoint.getMaxConnections(); }
+
+    public int getSendfileSize() { return ((AprEndpoint)endpoint).getSendfileSize(); }
+    public void setSendfileSize(int sendfileSize) { ((AprEndpoint)endpoint).setSendfileSize(sendfileSize); }
+
+    public void setSendfileThreadCount(int sendfileThreadCount) { ((AprEndpoint)endpoint).setSendfileThreadCount(sendfileThreadCount); }
+    public int getSendfileThreadCount() { return ((AprEndpoint)endpoint).getSendfileThreadCount(); }
+
+    public boolean getDeferAccept() { return ((AprEndpoint)endpoint).getDeferAccept(); }
+    public void setDeferAccept(boolean deferAccept) { ((AprEndpoint)endpoint).setDeferAccept(deferAccept); }
 
     // --------------------  SSL related properties --------------------
 
     /**
      * SSL protocol.
      */
-    public String getSSLProtocol() { return ((AprEndpoint)getEndpoint()).getSSLProtocol(); }
-    public void setSSLProtocol(String SSLProtocol) { ((AprEndpoint)getEndpoint()).setSSLProtocol(SSLProtocol); }
+    public String getSSLProtocol() { return ((AprEndpoint)endpoint).getSSLProtocol(); }
+    public void setSSLProtocol(String SSLProtocol) { ((AprEndpoint)endpoint).setSSLProtocol(SSLProtocol); }
 
 
     /**
      * SSL password (if a cert is encrypted, and no password has been provided, a callback
      * will ask for a password).
      */
-    public String getSSLPassword() { return ((AprEndpoint)getEndpoint()).getSSLPassword(); }
-    public void setSSLPassword(String SSLPassword) { ((AprEndpoint)getEndpoint()).setSSLPassword(SSLPassword); }
+    public String getSSLPassword() { return ((AprEndpoint)endpoint).getSSLPassword(); }
+    public void setSSLPassword(String SSLPassword) { ((AprEndpoint)endpoint).setSSLPassword(SSLPassword); }
 
 
     /**
      * SSL cipher suite.
      */
-    public String getSSLCipherSuite() { return ((AprEndpoint)getEndpoint()).getSSLCipherSuite(); }
-    public void setSSLCipherSuite(String SSLCipherSuite) { ((AprEndpoint)getEndpoint()).setSSLCipherSuite(SSLCipherSuite); }
-    public String[] getCiphersUsed() { return getEndpoint().getCiphersUsed();}
+    public String getSSLCipherSuite() { return ((AprEndpoint)endpoint).getSSLCipherSuite(); }
+    public void setSSLCipherSuite(String SSLCipherSuite) { ((AprEndpoint)endpoint).setSSLCipherSuite(SSLCipherSuite); }
+    public String[] getCiphersUsed() { return endpoint.getCiphersUsed();}
 
     /**
      * SSL honor cipher order.
@@ -111,94 +124,90 @@
      * instead of the default which is to allow the client to choose a
      * preferred cipher.
      */
-    public boolean getSSLHonorCipherOrder() { return ((AprEndpoint)getEndpoint()).getSSLHonorCipherOrder(); }
-    public void setSSLHonorCipherOrder(boolean SSLHonorCipherOrder) { ((AprEndpoint)getEndpoint()).setSSLHonorCipherOrder(SSLHonorCipherOrder); }
+    public boolean getSSLHonorCipherOrder() { return ((AprEndpoint)endpoint).getSSLHonorCipherOrder(); }
+    public void setSSLHonorCipherOrder(boolean SSLHonorCipherOrder) { ((AprEndpoint)endpoint).setSSLHonorCipherOrder(SSLHonorCipherOrder); }
 
 
     /**
      * SSL certificate file.
      */
-    public String getSSLCertificateFile() { return ((AprEndpoint)getEndpoint()).getSSLCertificateFile(); }
-    public void setSSLCertificateFile(String SSLCertificateFile) { ((AprEndpoint)getEndpoint()).setSSLCertificateFile(SSLCertificateFile); }
+    public String getSSLCertificateFile() { return ((AprEndpoint)endpoint).getSSLCertificateFile(); }
+    public void setSSLCertificateFile(String SSLCertificateFile) { ((AprEndpoint)endpoint).setSSLCertificateFile(SSLCertificateFile); }
 
 
     /**
      * SSL certificate key file.
      */
-    public String getSSLCertificateKeyFile() { return ((AprEndpoint)getEndpoint()).getSSLCertificateKeyFile(); }
-    public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { ((AprEndpoint)getEndpoint()).setSSLCertificateKeyFile(SSLCertificateKeyFile); }
+    public String getSSLCertificateKeyFile() { return ((AprEndpoint)endpoint).getSSLCertificateKeyFile(); }
+    public void setSSLCertificateKeyFile(String SSLCertificateKeyFile) { ((AprEndpoint)endpoint).setSSLCertificateKeyFile(SSLCertificateKeyFile); }
 
 
     /**
      * SSL certificate chain file.
      */
-    public String getSSLCertificateChainFile() { return ((AprEndpoint)getEndpoint()).getSSLCertificateChainFile(); }
-    public void setSSLCertificateChainFile(String SSLCertificateChainFile) { ((AprEndpoint)getEndpoint()).setSSLCertificateChainFile(SSLCertificateChainFile); }
+    public String getSSLCertificateChainFile() { return ((AprEndpoint)endpoint).getSSLCertificateChainFile(); }
+    public void setSSLCertificateChainFile(String SSLCertificateChainFile) { ((AprEndpoint)endpoint).setSSLCertificateChainFile(SSLCertificateChainFile); }
 
 
     /**
      * SSL CA certificate path.
      */
-    public String getSSLCACertificatePath() { return ((AprEndpoint)getEndpoint()).getSSLCACertificatePath(); }
-    public void setSSLCACertificatePath(String SSLCACertificatePath) { ((AprEndpoint)getEndpoint()).setSSLCACertificatePath(SSLCACertificatePath); }
+    public String getSSLCACertificatePath() { return ((AprEndpoint)endpoint).getSSLCACertificatePath(); }
+    public void setSSLCACertificatePath(String SSLCACertificatePath) { ((AprEndpoint)endpoint).setSSLCACertificatePath(SSLCACertificatePath); }
 
 
     /**
      * SSL CA certificate file.
      */
-    public String getSSLCACertificateFile() { return ((AprEndpoint)getEndpoint()).getSSLCACertificateFile(); }
-    public void setSSLCACertificateFile(String SSLCACertificateFile) { ((AprEndpoint)getEndpoint()).setSSLCACertificateFile(SSLCACertificateFile); }
+    public String getSSLCACertificateFile() { return ((AprEndpoint)endpoint).getSSLCACertificateFile(); }
+    public void setSSLCACertificateFile(String SSLCACertificateFile) { ((AprEndpoint)endpoint).setSSLCACertificateFile(SSLCACertificateFile); }
 
 
     /**
      * SSL CA revocation path.
      */
-    public String getSSLCARevocationPath() { return ((AprEndpoint)getEndpoint()).getSSLCARevocationPath(); }
-    public void setSSLCARevocationPath(String SSLCARevocationPath) { ((AprEndpoint)getEndpoint()).setSSLCARevocationPath(SSLCARevocationPath); }
+    public String getSSLCARevocationPath() { return ((AprEndpoint)endpoint).getSSLCARevocationPath(); }
+    public void setSSLCARevocationPath(String SSLCARevocationPath) { ((AprEndpoint)endpoint).setSSLCARevocationPath(SSLCARevocationPath); }
 
 
     /**
      * SSL CA revocation file.
      */
-    public String getSSLCARevocationFile() { return ((AprEndpoint)getEndpoint()).getSSLCARevocationFile(); }
-    public void setSSLCARevocationFile(String SSLCARevocationFile) { ((AprEndpoint)getEndpoint()).setSSLCARevocationFile(SSLCARevocationFile); }
+    public String getSSLCARevocationFile() { return ((AprEndpoint)endpoint).getSSLCARevocationFile(); }
+    public void setSSLCARevocationFile(String SSLCARevocationFile) { ((AprEndpoint)endpoint).setSSLCARevocationFile(SSLCARevocationFile); }
 
 
     /**
      * SSL verify client.
      */
-    public String getSSLVerifyClient() { return ((AprEndpoint)getEndpoint()).getSSLVerifyClient(); }
-    public void setSSLVerifyClient(String SSLVerifyClient) { ((AprEndpoint)getEndpoint()).setSSLVerifyClient(SSLVerifyClient); }
+    public String getSSLVerifyClient() { return ((AprEndpoint)endpoint).getSSLVerifyClient(); }
+    public void setSSLVerifyClient(String SSLVerifyClient) { ((AprEndpoint)endpoint).setSSLVerifyClient(SSLVerifyClient); }
 
 
     /**
      * SSL verify depth.
      */
-    public int getSSLVerifyDepth() { return ((AprEndpoint)getEndpoint()).getSSLVerifyDepth(); }
-    public void setSSLVerifyDepth(int SSLVerifyDepth) { ((AprEndpoint)getEndpoint()).setSSLVerifyDepth(SSLVerifyDepth); }
+    public int getSSLVerifyDepth() { return ((AprEndpoint)endpoint).getSSLVerifyDepth(); }
+    public void setSSLVerifyDepth(int SSLVerifyDepth) { ((AprEndpoint)endpoint).setSSLVerifyDepth(SSLVerifyDepth); }
 
     /**
      * Disable SSL compression.
      */
-    public boolean getSSLDisableCompression() { return ((AprEndpoint)getEndpoint()).getSSLDisableCompression(); }
-    public void setSSLDisableCompression(boolean disable) { ((AprEndpoint)getEndpoint()).setSSLDisableCompression(disable); }
+    public boolean getSSLDisableCompression() { return ((AprEndpoint)endpoint).getSSLDisableCompression(); }
+    public void setSSLDisableCompression(boolean disable) { ((AprEndpoint)endpoint).setSSLDisableCompression(disable); }
 
     /**
      * Disable TLS Session Tickets (RFC 4507).
      */
-    public boolean getSSLDisableSessionTickets() { return ((AprEndpoint)getEndpoint()).getSSLDisableSessionTickets(); }
-    public void setSSLDisableSessionTickets(boolean enable) { ((AprEndpoint)getEndpoint()).setSSLDisableSessionTickets(enable); }
+    public boolean getSSLDisableSessionTickets() { return ((AprEndpoint)endpoint).getSSLDisableSessionTickets(); }
+    public void setSSLDisableSessionTickets(boolean enable) { ((AprEndpoint)endpoint).setSSLDisableSessionTickets(enable); }
 
 
     // ----------------------------------------------------- JMX related methods
 
     @Override
     protected String getNamePrefix() {
-        if (isSSLEnabled()) {
-            return ("https-apr");
-        } else {
-            return ("http-apr");
-        }
+        return ("http-apr");
     }
 
 
@@ -206,15 +215,15 @@
     public void start() throws Exception {
         super.start();
         if (npnHandler != null) {
-            long sslCtx = ((AprEndpoint) getEndpoint()).getJniSslContext();
-            npnHandler.init(getEndpoint(), sslCtx, getAdapter());
+            long sslCtx = ((AprEndpoint) endpoint).getJniSslContext();
+            npnHandler.init(endpoint, sslCtx, getAdapter());
         }
     }
 
     // --------------------  Connection handler --------------------
 
     protected static class Http11ConnectionHandler
-            extends AbstractConnectionHandler<Long,Http11AprProcessor> {
+            extends AbstractConnectionHandler<Long,Http11AprProcessor> implements Handler {
 
         protected Http11AprProtocol proto;
 
@@ -242,20 +251,20 @@
          * @param addToPoller
          */
         @Override
-        public void release(SocketWrapperBase<Long> socket,
+        public void release(SocketWrapper<Long> socket,
                 Processor<Long> processor, boolean isSocketClosing,
                 boolean addToPoller) {
             processor.recycle(isSocketClosing);
             recycledProcessors.push(processor);
-            if (addToPoller && proto.getEndpoint().isRunning()) {
-                ((AprEndpoint)proto.getEndpoint()).getPoller().add(
+            if (addToPoller && proto.endpoint.isRunning()) {
+                ((AprEndpoint)proto.endpoint).getPoller().add(
                         socket.getSocket().longValue(),
-                        proto.getEndpoint().getKeepAliveTimeout(), true, false);
+                        proto.endpoint.getKeepAliveTimeout(), true, false);
             }
         }
 
         @Override
-        public SocketState process(SocketWrapperBase<Long> socket,
+        public SocketState process(SocketWrapper<Long> socket,
                 SocketStatus status) {
             if (proto.npnHandler != null) {
                 Processor<Long> processor = null;
@@ -264,7 +273,7 @@
 
                 }
                 if (processor == null) {
-                    // if not null - handled by http11
+                    // if not null - this is a former comet request, handled by http11
                     SocketState socketState = proto.npnHandler.process(socket, status);
                     // handled by npn protocol.
                     if (socketState == SocketState.CLOSED ||
@@ -277,21 +286,34 @@
         }
 
         @Override
-        protected void initSsl(SocketWrapperBase<Long> socket,
+        protected void initSsl(SocketWrapper<Long> socket,
                 Processor<Long> processor) {
             // NOOP for APR
         }
 
         @Override
-        protected void longPoll(SocketWrapperBase<Long> socket,
+        protected void longPoll(SocketWrapper<Long> socket,
                 Processor<Long> processor) {
 
             if (processor.isAsync()) {
                 // Async
                 socket.setAsync(true);
+            } else if (processor.isComet()) {
+                // Comet
+                if (proto.endpoint.isRunning()) {
+                    socket.setComet(true);
+                    ((AprEndpoint) proto.endpoint).getPoller().add(
+                            socket.getSocket().longValue(),
+                            proto.endpoint.getSoTimeout(), true, false);
+                } else {
+                    // Process a STOP directly
+                    ((AprEndpoint) proto.endpoint).processSocket(
+                            socket.getSocket().longValue(),
+                            SocketStatus.STOP);
+                }
             } else {
                 // Upgraded
-                Poller p = ((AprEndpoint) proto.getEndpoint()).getPoller();
+                Poller p = ((AprEndpoint) proto.endpoint).getPoller();
                 if (p == null) {
                     // Connector has been stopped
                     release(socket, processor, true, false);
@@ -304,7 +326,7 @@
         @Override
         protected Http11AprProcessor createProcessor() {
             Http11AprProcessor processor = new Http11AprProcessor(
-                    proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.getEndpoint(),
+                    proto.getMaxHttpHeaderSize(), (AprEndpoint)proto.endpoint,
                     proto.getMaxTrailerSize(), proto.getMaxExtensionSize(),
                     proto.getMaxSwallowSize());
             proto.configureProcessor(processor);
@@ -316,10 +338,11 @@
 
         @Override
         protected Processor<Long> createUpgradeProcessor(
-                SocketWrapperBase<Long> socket, ByteBuffer leftoverInput,
+                SocketWrapper<Long> socket, ByteBuffer leftoverInput,
                 HttpUpgradeHandler httpUpgradeProcessor)
                 throws IOException {
-            return new UpgradeProcessor<>(socket, leftoverInput, httpUpgradeProcessor,
+            return new AprProcessor(socket, leftoverInput,
+                    httpUpgradeProcessor, (AprEndpoint) proto.endpoint,
                     proto.getUpgradeAsyncWriteBufferSize());
         }
     }
diff --git a/java/org/apache/coyote/http11/Http11Nio2Processor.java b/java/org/apache/coyote/http11/Http11Nio2Processor.java
index 658412f..6abd200 100644
--- a/java/org/apache/coyote/http11/Http11Nio2Processor.java
+++ b/java/org/apache/coyote/http11/Http11Nio2Processor.java
@@ -17,6 +17,7 @@
 package org.apache.coyote.http11;
 
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.net.InetAddress;
 import java.net.InetSocketAddress;
 
@@ -24,16 +25,18 @@
 
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.ErrorState;
+import org.apache.coyote.RequestInfo;
 import org.apache.coyote.http11.filters.BufferedInputFilter;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.Nio2Channel;
 import org.apache.tomcat.util.net.Nio2Endpoint;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SecureNio2Channel;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -62,10 +65,10 @@
         super(endpoint);
 
         inputBuffer = new InternalNio2InputBuffer(request, maxHttpHeaderSize);
-        request.setInputBuffer(getInputBuffer());
+        request.setInputBuffer(inputBuffer);
 
         outputBuffer = new InternalNio2OutputBuffer(response, maxHttpHeaderSize);
-        response.setOutputBuffer(getOutputBuffer());
+        response.setOutputBuffer(outputBuffer);
 
         initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
     }
@@ -82,9 +85,75 @@
     // --------------------------------------------------------- Public Methods
 
     @Override
+    public SocketState event(SocketStatus status)
+        throws IOException {
+
+        long soTimeout = endpoint.getSoTimeout();
+
+        RequestInfo rp = request.getRequestProcessor();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            if (!getAdapter().event(request, response, status)) {
+                setErrorState(ErrorState.CLOSE_NOW, null);
+            }
+            if (!getErrorState().isError()) {
+                if (socketWrapper != null) {
+                    socketWrapper.setComet(comet);
+                    if (comet) {
+                        Integer comettimeout = (Integer) request.getAttribute(
+                                org.apache.coyote.Constants.COMET_TIMEOUT_ATTR);
+                        if (comettimeout != null) {
+                            socketWrapper.setTimeout(comettimeout.longValue());
+                        }
+                    } else {
+                        //reset the timeout
+                        if (keepAlive) {
+                            socketWrapper.setTimeout(keepAliveTimeout);
+                        } else {
+                            socketWrapper.setTimeout(soTimeout);
+                        }
+                    }
+
+                }
+            }
+        } catch (InterruptedIOException e) {
+            setErrorState(ErrorState.CLOSE_NOW, e);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            setErrorState(ErrorState.CLOSE_NOW, t);
+            getAdapter().log(request, response, 0);
+            log.error(sm.getString("http11processor.request.process"), t);
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (getErrorState().isError() || status==SocketStatus.STOP) {
+            return SocketState.CLOSED;
+        } else if (!comet) {
+            if (keepAlive) {
+                inputBuffer.nextRequest();
+                outputBuffer.nextRequest();
+                if (((InternalNio2InputBuffer) inputBuffer).isPending()) {
+                    // Following comet processing, a read is still pending, so
+                    // keep the processor associated
+                    return SocketState.LONG;
+                } else {
+                    return SocketState.OPEN;
+                }
+            } else {
+                return SocketState.CLOSED;
+            }
+        } else {
+            return SocketState.LONG;
+        }
+    }
+
+    @Override
     public SocketState asyncDispatch(SocketStatus status) {
         SocketState state = super.asyncDispatch(status);
-        if (state == SocketState.OPEN && ((InternalNio2InputBuffer) getInputBuffer()).isPending()) {
+        if (state == SocketState.OPEN && ((InternalNio2InputBuffer) inputBuffer).isPending()) {
             // Following async processing, a read is still pending, so
             // keep the processor associated
             return SocketState.LONG;
@@ -96,10 +165,10 @@
     @Override
     protected void registerForEvent(boolean read, boolean write) {
         if (read) {
-            ((InternalNio2InputBuffer) getInputBuffer()).registerReadInterest();
+            ((InternalNio2InputBuffer) inputBuffer).registerReadInterest();
         }
         if (write) {
-            ((InternalNio2OutputBuffer) getOutputBuffer()).registerWriteInterest();
+            ((InternalNio2OutputBuffer) outputBuffer).registerWriteInterest();
         }
     }
 
@@ -121,14 +190,41 @@
 
 
     @Override
+    protected boolean disableKeepAlive() {
+        return false;
+    }
+
+
+    @Override
+    protected void setRequestLineReadTimeout() throws IOException {
+        // socket.setTimeout()
+        //     - timeout used by poller
+        // socket.getSocket().getIOChannel().socket().setSoTimeout()
+        //     - timeout used for blocking reads
+
+        // When entering the processing loop there will always be data to read
+        // so no point changing timeouts at this point
+
+        // For the second and subsequent executions of the processing loop, a
+        // non-blocking read is used so again no need to set the timeouts
+
+        // Because NIO supports non-blocking reading of the request line and
+        // headers the timeouts need to be set when returning the socket to
+        // the poller rather than here.
+
+        // NO-OP
+    }
+
+
+    @Override
     protected boolean handleIncompleteRequestLineRead() {
         // Haven't finished reading the request so keep the socket
         // open
         openSocket = true;
         // Check to see if we have read any of the request line yet
         if (((InternalNio2InputBuffer)
-                getInputBuffer()).getParsingRequestLinePhase() < 1) {
-            if (keptAlive) {
+                inputBuffer).getParsingRequestLinePhase() < 1) {
+            if (socketWrapper.getLastAccess() > -1 || keptAlive) {
                 // Haven't read the request line and have previously processed a
                 // request. Must be keep-alive. Make sure poller uses keepAlive.
                 socketWrapper.setTimeout(endpoint.getKeepAliveTimeout());
@@ -162,8 +258,23 @@
 
 
     @Override
+    protected void setCometTimeouts(SocketWrapper<Nio2Channel> socketWrapper) {
+        if (socketWrapper != null)  {
+            socketWrapper.setComet(comet);
+            if (comet) {
+                Integer comettimeout = (Integer) request.getAttribute(
+                        org.apache.coyote.Constants.COMET_TIMEOUT_ATTR);
+                if (comettimeout != null) {
+                    socketWrapper.setTimeout(comettimeout.longValue());
+                }
+            }
+        }
+    }
+
+
+    @Override
     protected boolean breakKeepAliveLoop(
-            SocketWrapperBase<Nio2Channel> socketWrapper) {
+            SocketWrapper<Nio2Channel> socketWrapper) {
         openSocket = keepAlive;
         // Do sendfile as needed: add socket to sendfile and end
         if (sendfileData != null && !getErrorState().isError()) {
@@ -191,6 +302,7 @@
 
     @Override
     public void recycleInternal() {
+        socketWrapper = null;
         sendfileData = null;
     }
 
@@ -357,10 +469,10 @@
                  * Consume and buffer the request body, so that it does not
                  * interfere with the client's handshake messages
                  */
-                InputFilter[] inputFilters = getInputBuffer().getFilters();
+                InputFilter[] inputFilters = inputBuffer.getFilters();
                 ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
                     .setLimit(maxSavePostSize);
-                getInputBuffer().addActiveFilter
+                inputBuffer.addActiveFilter
                     (inputFilters[Constants.BUFFERED_FILTER]);
                 SecureNio2Channel sslChannel = (SecureNio2Channel) socketWrapper.getSocket();
                 SSLEngine engine = sslChannel.getSslEngine();
@@ -391,6 +503,42 @@
             }
             break;
         }
+        case COMET_BEGIN: {
+            comet = true;
+            break;
+        }
+        case COMET_END: {
+            comet = false;
+            break;
+        }
+        case COMET_CLOSE: {
+            if (socketWrapper == null || socketWrapper.getSocket() == null) {
+                return;
+            }
+            RequestInfo rp = request.getRequestProcessor();
+            if (rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE) {
+                // Close event for this processor triggered by request
+                // processing in another processor, a non-Tomcat thread (i.e.
+                // an application controlled thread) or similar.
+                endpoint.processSocket(this.socketWrapper, SocketStatus.OPEN_READ, true);
+            }
+            break;
+        }
+        case COMET_SETTIMEOUT: {
+            if (param == null) {
+                return;
+            }
+            if (socketWrapper == null) {
+                return;
+            }
+            long timeout = ((Long)param).longValue();
+            //if we are not piggy backing on a worker thread, set the timeout
+            RequestInfo rp = request.getRequestProcessor();
+            if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) {
+                socketWrapper.setTimeout(timeout);
+            }
+            break;
+        }
         }
     }
 
@@ -409,7 +557,7 @@
                 org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
         if (fileName != null) {
             // No entity body sent here
-            getOutputBuffer().addActiveFilter(outputFilters[Constants.VOID_FILTER]);
+            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
             contentDelimitation = true;
             sendfileData = new Nio2Endpoint.SendfileData();
             sendfileData.fileName = fileName;
@@ -422,6 +570,16 @@
         return false;
     }
 
+    @Override
+    protected AbstractInputBuffer<Nio2Channel> getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer<Nio2Channel> getOutputBuffer() {
+        return outputBuffer;
+    }
+
     /**
      * Set the SSL information for this HTTP connection.
      */
diff --git a/java/org/apache/coyote/http11/Http11Nio2Protocol.java b/java/org/apache/coyote/http11/Http11Nio2Protocol.java
index 3744608..64edf2e 100644
--- a/java/org/apache/coyote/http11/Http11Nio2Protocol.java
+++ b/java/org/apache/coyote/http11/Http11Nio2Protocol.java
@@ -25,9 +25,10 @@
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeProcessor;
+import org.apache.coyote.http11.upgrade.Nio2Processor;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.Nio2Channel;
 import org.apache.tomcat.util.net.Nio2Endpoint;
 import org.apache.tomcat.util.net.Nio2Endpoint.Handler;
@@ -35,7 +36,7 @@
 import org.apache.tomcat.util.net.SSLImplementation;
 import org.apache.tomcat.util.net.SecureNio2Channel;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -46,19 +47,31 @@
     private static final Log log = LogFactory.getLog(Http11Nio2Protocol.class);
 
 
-    public Http11Nio2Protocol() {
-        super(new Nio2Endpoint());
-        Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
-        setHandler(cHandler);
-        ((Nio2Endpoint) getEndpoint()).setHandler(cHandler);
-    }
-
-
     @Override
     protected Log getLog() { return log; }
 
 
     @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    public Http11Nio2Protocol() {
+        endpoint=new Nio2Endpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((Nio2Endpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+    }
+
+
+    public Nio2Endpoint getEndpoint() {
+        return ((Nio2Endpoint)endpoint);
+    }
+
+    @Override
     public void start() throws Exception {
         super.start();
         if (npnHandler != null) {
@@ -66,40 +79,47 @@
         }
     }
 
+    // -------------------- Properties--------------------
+
+    private final Http11ConnectionHandler cHandler;
 
     // -------------------- Pool setup --------------------
 
     public void setAcceptorThreadPriority(int threadPriority) {
-        ((Nio2Endpoint)getEndpoint()).setAcceptorThreadPriority(threadPriority);
+        ((Nio2Endpoint)endpoint).setAcceptorThreadPriority(threadPriority);
+    }
+
+    public void setPollerThreadPriority(int threadPriority) {
+        ((Nio2Endpoint)endpoint).setPollerThreadPriority(threadPriority);
     }
 
     public int getAcceptorThreadPriority() {
-      return ((Nio2Endpoint)getEndpoint()).getAcceptorThreadPriority();
+      return ((Nio2Endpoint)endpoint).getAcceptorThreadPriority();
+    }
+
+    public int getPollerThreadPriority() {
+      return ((Nio2Endpoint)endpoint).getThreadPriority();
     }
 
     public boolean getUseSendfile() {
-        return getEndpoint().getUseSendfile();
+        return endpoint.getUseSendfile();
     }
 
     public void setUseSendfile(boolean useSendfile) {
-        ((Nio2Endpoint)getEndpoint()).setUseSendfile(useSendfile);
+        ((Nio2Endpoint)endpoint).setUseSendfile(useSendfile);
     }
 
     // -------------------- Tcp setup --------------------
 
     public void setOomParachute(int oomParachute) {
-        ((Nio2Endpoint)getEndpoint()).setOomParachute(oomParachute);
+        ((Nio2Endpoint)endpoint).setOomParachute(oomParachute);
     }
 
     // ----------------------------------------------------- JMX related methods
 
     @Override
     protected String getNamePrefix() {
-        if (isSSLEnabled()) {
-            return ("https-nio2");
-        } else {
-            return ("http-nio2");
-        }
+        return ("http-nio2");
     }
 
 
@@ -136,7 +156,7 @@
          * close, errors etc.
          */
         @Override
-        public void release(SocketWrapperBase<Nio2Channel> socket) {
+        public void release(SocketWrapper<Nio2Channel> socket) {
             Processor<Nio2Channel> processor =
                 connections.remove(socket.getSocket());
             if (processor != null) {
@@ -146,7 +166,7 @@
         }
 
         @Override
-        public SocketState process(SocketWrapperBase<Nio2Channel> socket,
+        public SocketState process(SocketWrapper<Nio2Channel> socket,
                 SocketStatus status) {
             if (proto.npnHandler != null) {
                 SocketState ss = proto.npnHandler.process(socket, status);
@@ -168,22 +188,22 @@
          * @param addToPoller
          */
         @Override
-        public void release(SocketWrapperBase<Nio2Channel> socket,
+        public void release(SocketWrapper<Nio2Channel> socket,
                 Processor<Nio2Channel> processor, boolean isSocketClosing,
                 boolean addToPoller) {
             processor.recycle(isSocketClosing);
             recycledProcessors.push(processor);
             if (socket.isAsync()) {
-                ((Nio2Endpoint) proto.getEndpoint()).removeTimeout(socket);
+                ((Nio2Endpoint) proto.endpoint).removeTimeout(socket);
             }
             if (addToPoller) {
-                ((Nio2Endpoint) proto.getEndpoint()).awaitBytes(socket);
+                ((Nio2Endpoint) proto.endpoint).awaitBytes(socket);
             }
         }
 
 
         @Override
-        protected void initSsl(SocketWrapperBase<Nio2Channel> socket,
+        protected void initSsl(SocketWrapper<Nio2Channel> socket,
                 Processor<Nio2Channel> processor) {
             if (proto.isSSLEnabled() &&
                     (proto.sslImplementation != null)
@@ -199,15 +219,15 @@
         }
 
         @Override
-        protected void longPoll(SocketWrapperBase<Nio2Channel> socket,
+        protected void longPoll(SocketWrapper<Nio2Channel> socket,
                 Processor<Nio2Channel> processor) {
             if (processor.isAsync()) {
                 socket.setAsync(true);
-                ((Nio2Endpoint) proto.getEndpoint()).addTimeout(socket);
+                ((Nio2Endpoint) proto.endpoint).addTimeout(socket);
             } else if (processor.isUpgrade()) {
                 if (((Nio2SocketWrapper) socket).isUpgradeInit()) {
                     try {
-                        ((Nio2Endpoint) proto.getEndpoint()).awaitBytes(socket);
+                        ((Nio2Endpoint) proto.endpoint).awaitBytes(socket);
                     } catch (ReadPendingException e) {
                         // Ignore, the initial state after upgrade is
                         // impossible to predict, and a read must be pending
@@ -216,6 +236,7 @@
                 }
             } else {
                 // Either:
+                //  - this is comet request
                 //  - this is an upgraded connection
                 //  - the request line/headers have not been completely
                 //    read
@@ -227,7 +248,7 @@
         @Override
         public Http11Nio2Processor createProcessor() {
             Http11Nio2Processor processor = new Http11Nio2Processor(
-                    proto.getMaxHttpHeaderSize(), (Nio2Endpoint) proto.getEndpoint(),
+                    proto.getMaxHttpHeaderSize(), (Nio2Endpoint) proto.endpoint,
                     proto.getMaxTrailerSize(), proto.getMaxExtensionSize(),
                     proto.getMaxSwallowSize());
             proto.configureProcessor(processor);
@@ -237,10 +258,10 @@
 
         @Override
         protected Processor<Nio2Channel> createUpgradeProcessor(
-                SocketWrapperBase<Nio2Channel> socket, ByteBuffer leftoverInput,
+                SocketWrapper<Nio2Channel> socket, ByteBuffer leftoverInput,
                 HttpUpgradeHandler httpUpgradeProcessor)
                 throws IOException {
-            return new UpgradeProcessor<>(socket, leftoverInput,
+            return new Nio2Processor(proto.endpoint, socket, leftoverInput,
                     httpUpgradeProcessor, proto.getUpgradeAsyncWriteBufferSize());
         }
 
@@ -254,7 +275,7 @@
         @Override
         public void closeAll() {
             for (Nio2Channel channel : connections.keySet()) {
-                ((Nio2Endpoint) proto.getEndpoint()).closeSocket(channel.getSocket());
+                ((Nio2Endpoint) proto.endpoint).closeSocket(channel.getSocket(), SocketStatus.STOP);
             }
         }
     }
diff --git a/java/org/apache/coyote/http11/Http11NioProcessor.java b/java/org/apache/coyote/http11/Http11NioProcessor.java
index 3ca73d7..7563a20 100644
--- a/java/org/apache/coyote/http11/Http11NioProcessor.java
+++ b/java/org/apache/coyote/http11/Http11NioProcessor.java
@@ -17,6 +17,7 @@
 package org.apache.coyote.http11;
 
 import java.io.IOException;
+import java.io.InterruptedIOException;
 import java.net.InetAddress;
 import java.nio.channels.SelectionKey;
 
@@ -24,15 +25,19 @@
 
 import org.apache.coyote.ActionCode;
 import org.apache.coyote.ErrorState;
+import org.apache.coyote.RequestInfo;
 import org.apache.coyote.http11.filters.BufferedInputFilter;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.NioChannel;
 import org.apache.tomcat.util.net.NioEndpoint;
-import org.apache.tomcat.util.net.NioEndpoint.NioSocketWrapper;
+import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SecureNioChannel;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -63,10 +68,10 @@
         super(endpoint);
 
         inputBuffer = new InternalNioInputBuffer(request, maxHttpHeaderSize);
-        request.setInputBuffer(getInputBuffer());
+        request.setInputBuffer(inputBuffer);
 
         outputBuffer = new InternalNioOutputBuffer(response, maxHttpHeaderSize);
-        response.setOutputBuffer(getOutputBuffer());
+        response.setOutputBuffer(outputBuffer);
 
         initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
     }
@@ -82,6 +87,73 @@
 
     // --------------------------------------------------------- Public Methods
 
+    /**
+     * Process pipelined HTTP requests using the specified input and output
+     * streams.
+     *
+     * @throws IOException error during an I/O operation
+     */
+    @Override
+    public SocketState event(SocketStatus status) throws IOException {
+
+        long soTimeout = endpoint.getSoTimeout();
+
+        RequestInfo rp = request.getRequestProcessor();
+        final NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socketWrapper.getSocket().getAttachment();
+        try {
+            rp.setStage(org.apache.coyote.Constants.STAGE_SERVICE);
+            if (!getAdapter().event(request, response, status)) {
+                setErrorState(ErrorState.CLOSE_NOW, null);
+            }
+            if (!getErrorState().isError()) {
+                if (attach != null) {
+                    attach.setComet(comet);
+                    if (comet) {
+                        Integer comettimeout = (Integer) request.getAttribute(
+                                org.apache.coyote.Constants.COMET_TIMEOUT_ATTR);
+                        if (comettimeout != null) {
+                            attach.setTimeout(comettimeout.longValue());
+                        }
+                    } else {
+                        //reset the timeout
+                        if (keepAlive) {
+                            attach.setTimeout(keepAliveTimeout);
+                        } else {
+                            attach.setTimeout(soTimeout);
+                        }
+                    }
+
+                }
+            }
+        } catch (InterruptedIOException e) {
+            setErrorState(ErrorState.CLOSE_NOW, e);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            // 500 - Internal Server Error
+            response.setStatus(500);
+            setErrorState(ErrorState.CLOSE_NOW, t);
+            log.error(sm.getString("http11processor.request.process"), t);
+            getAdapter().log(request, response, 0);
+        }
+
+        rp.setStage(org.apache.coyote.Constants.STAGE_ENDED);
+
+        if (getErrorState().isError() || status==SocketStatus.STOP) {
+            return SocketState.CLOSED;
+        } else if (!comet) {
+            if (keepAlive) {
+                inputBuffer.nextRequest();
+                outputBuffer.nextRequest();
+                return SocketState.OPEN;
+            } else {
+                return SocketState.CLOSED;
+            }
+        } else {
+            return SocketState.LONG;
+        }
+    }
+
+
     @Override
     protected void registerForEvent(boolean read, boolean write) {
         final NioChannel socket = socketWrapper.getSocket();
@@ -99,7 +171,7 @@
 
     @Override
     protected void resetTimeouts() {
-        final NioEndpoint.NioSocketWrapper attach = (NioEndpoint.NioSocketWrapper)socketWrapper.getSocket().getAttachment();
+        final NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socketWrapper.getSocket().getAttachment();
         if (!getErrorState().isError() && attach != null &&
                 asyncStateMachine.isAsyncDispatching()) {
             long soTimeout = endpoint.getSoTimeout();
@@ -115,14 +187,41 @@
 
 
     @Override
+    protected boolean disableKeepAlive() {
+        return false;
+    }
+
+
+    @Override
+    protected void setRequestLineReadTimeout() throws IOException {
+        // socket.setTimeout()
+        //     - timeout used by poller
+        // socket.getSocket().getIOChannel().socket().setSoTimeout()
+        //     - timeout used for blocking reads
+
+        // When entering the processing loop there will always be data to read
+        // so no point changing timeouts at this point
+
+        // For the second and subsequent executions of the processing loop, a
+        // non-blocking read is used so again no need to set the timeouts
+
+        // Because NIO supports non-blocking reading of the request line and
+        // headers the timeouts need to be set when returning the socket to
+        // the poller rather than here.
+
+        // NO-OP
+    }
+
+
+    @Override
     protected boolean handleIncompleteRequestLineRead() {
         // Haven't finished reading the request so keep the socket
         // open
         openSocket = true;
         // Check to see if we have read any of the request line yet
         if (((InternalNioInputBuffer)
-                getInputBuffer()).getParsingRequestLinePhase() < 2) {
-            if (keptAlive) {
+                inputBuffer).getParsingRequestLinePhase() < 2) {
+            if (socketWrapper.getLastAccess() > -1 || keptAlive) {
                 // Haven't read the request line and have previously processed a
                 // request. Must be keep-alive. Make sure poller uses keepAlive.
                 socketWrapper.setTimeout(endpoint.getKeepAliveTimeout());
@@ -152,17 +251,38 @@
 
 
     @Override
-    protected boolean breakKeepAliveLoop(SocketWrapperBase<NioChannel> socketWrapper) {
+    protected void setCometTimeouts(SocketWrapper<NioChannel> socketWrapper) {
+        // Comet support
+        SelectionKey key = socketWrapper.getSocket().getIOChannel().keyFor(
+                socketWrapper.getSocket().getPoller().getSelector());
+        if (key != null) {
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
+            if (attach != null)  {
+                attach.setComet(comet);
+                if (comet) {
+                    Integer comettimeout = (Integer) request.getAttribute(
+                            org.apache.coyote.Constants.COMET_TIMEOUT_ATTR);
+                    if (comettimeout != null) {
+                        attach.setTimeout(comettimeout.longValue());
+                    }
+                }
+            }
+        }
+    }
+
+
+    @Override
+    protected boolean breakKeepAliveLoop(SocketWrapper<NioChannel> socketWrapper) {
         openSocket = keepAlive;
         // Do sendfile as needed: add socket to sendfile and end
         if (sendfileData != null && !getErrorState().isError()) {
-            ((NioSocketWrapper) socketWrapper).setSendfileData(sendfileData);
+            ((KeyAttachment) socketWrapper).setSendfileData(sendfileData);
             sendfileData.keepAlive = keepAlive;
             SelectionKey key = socketWrapper.getSocket().getIOChannel().keyFor(
                     socketWrapper.getSocket().getPoller().getSelector());
             //do the first write on this thread, might as well
             if (socketWrapper.getSocket().getPoller().processSendfile(key,
-                    (NioSocketWrapper) socketWrapper, true)) {
+                    (KeyAttachment) socketWrapper, true)) {
                 sendfileInProgress = true;
             } else {
                 // Write failed
@@ -179,6 +299,7 @@
 
     @Override
     public void recycleInternal() {
+        socketWrapper = null;
         sendfileData = null;
     }
 
@@ -317,10 +438,10 @@
                  * Consume and buffer the request body, so that it does not
                  * interfere with the client's handshake messages
                  */
-                InputFilter[] inputFilters = getInputBuffer().getFilters();
+                InputFilter[] inputFilters = inputBuffer.getFilters();
                 ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
                     .setLimit(maxSavePostSize);
-                getInputBuffer().addActiveFilter
+                inputBuffer.addActiveFilter
                     (inputFilters[Constants.BUFFERED_FILTER]);
                 SecureNioChannel sslChannel = (SecureNioChannel) socketWrapper.getSocket();
                 SSLEngine engine = sslChannel.getSslEngine();
@@ -351,6 +472,43 @@
             }
             break;
         }
+        case COMET_BEGIN: {
+            comet = true;
+            break;
+        }
+        case COMET_END: {
+            comet = false;
+            break;
+        }
+        case COMET_CLOSE: {
+            if (socketWrapper==null || socketWrapper.getSocket().getAttachment()==null) {
+                return;
+            }
+            RequestInfo rp = request.getRequestProcessor();
+            if (rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE) {
+                // Close event for this processor triggered by request
+                // processing in another processor, a non-Tomcat thread (i.e.
+                // an application controlled thread) or similar.
+                socketWrapper.getSocket().getPoller().add(socketWrapper.getSocket());
+            }
+            break;
+        }
+        case COMET_SETTIMEOUT: {
+            if (param==null) {
+                return;
+            }
+            if (socketWrapper==null || socketWrapper.getSocket().getAttachment()==null) {
+                return;
+            }
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment)socketWrapper.getSocket().getAttachment();
+            long timeout = ((Long)param).longValue();
+            //if we are not piggy backing on a worker thread, set the timeout
+            RequestInfo rp = request.getRequestProcessor();
+            if ( rp.getStage() != org.apache.coyote.Constants.STAGE_SERVICE ) {
+                attach.setTimeout(timeout);
+            }
+            break;
+        }
         }
     }
 
@@ -369,7 +527,7 @@
                 org.apache.coyote.Constants.SENDFILE_FILENAME_ATTR);
         if (fileName != null) {
             // No entity body sent here
-            getOutputBuffer().addActiveFilter(outputFilters[Constants.VOID_FILTER]);
+            outputBuffer.addActiveFilter(outputFilters[Constants.VOID_FILTER]);
             contentDelimitation = true;
             sendfileData = new NioEndpoint.SendfileData();
             sendfileData.fileName = fileName;
@@ -382,6 +540,16 @@
         return false;
     }
 
+    @Override
+    protected AbstractInputBuffer<NioChannel> getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer<NioChannel> getOutputBuffer() {
+        return outputBuffer;
+    }
+
     /**
      * Set the SSL information for this HTTP connection.
      */
diff --git a/java/org/apache/coyote/http11/Http11NioProtocol.java b/java/org/apache/coyote/http11/Http11NioProtocol.java
index 19d66d3..0d421a3 100644
--- a/java/org/apache/coyote/http11/Http11NioProtocol.java
+++ b/java/org/apache/coyote/http11/Http11NioProtocol.java
@@ -26,16 +26,17 @@
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.Processor;
-import org.apache.coyote.http11.upgrade.UpgradeProcessor;
+import org.apache.coyote.http11.upgrade.NioProcessor;
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.NioChannel;
 import org.apache.tomcat.util.net.NioEndpoint;
 import org.apache.tomcat.util.net.NioEndpoint.Handler;
 import org.apache.tomcat.util.net.SSLImplementation;
 import org.apache.tomcat.util.net.SecureNioChannel;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 
 /**
@@ -51,19 +52,31 @@
     private static final Log log = LogFactory.getLog(Http11NioProtocol.class);
 
 
-    public Http11NioProtocol() {
-        super(new NioEndpoint());
-        Http11ConnectionHandler cHandler = new Http11ConnectionHandler(this);
-        setHandler(cHandler);
-        ((NioEndpoint) getEndpoint()).setHandler(cHandler);
-    }
-
-
     @Override
     protected Log getLog() { return log; }
 
 
     @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    public Http11NioProtocol() {
+        endpoint=new NioEndpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((NioEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+    }
+
+
+    public NioEndpoint getEndpoint() {
+        return ((NioEndpoint)endpoint);
+    }
+
+    @Override
     public void start() throws Exception {
         super.start();
         if (npnHandler != null) {
@@ -71,64 +84,63 @@
         }
     }
 
+    // -------------------- Properties--------------------
+
+    private final Http11ConnectionHandler cHandler;
 
     // -------------------- Pool setup --------------------
 
     public void setPollerThreadCount(int count) {
-        ((NioEndpoint)getEndpoint()).setPollerThreadCount(count);
+        ((NioEndpoint)endpoint).setPollerThreadCount(count);
     }
 
     public int getPollerThreadCount() {
-        return ((NioEndpoint)getEndpoint()).getPollerThreadCount();
+        return ((NioEndpoint)endpoint).getPollerThreadCount();
     }
 
     public void setSelectorTimeout(long timeout) {
-        ((NioEndpoint)getEndpoint()).setSelectorTimeout(timeout);
+        ((NioEndpoint)endpoint).setSelectorTimeout(timeout);
     }
 
     public long getSelectorTimeout() {
-        return ((NioEndpoint)getEndpoint()).getSelectorTimeout();
+        return ((NioEndpoint)endpoint).getSelectorTimeout();
     }
 
     public void setAcceptorThreadPriority(int threadPriority) {
-        ((NioEndpoint)getEndpoint()).setAcceptorThreadPriority(threadPriority);
+        ((NioEndpoint)endpoint).setAcceptorThreadPriority(threadPriority);
     }
 
     public void setPollerThreadPriority(int threadPriority) {
-        ((NioEndpoint)getEndpoint()).setPollerThreadPriority(threadPriority);
+        ((NioEndpoint)endpoint).setPollerThreadPriority(threadPriority);
     }
 
     public int getAcceptorThreadPriority() {
-      return ((NioEndpoint)getEndpoint()).getAcceptorThreadPriority();
+      return ((NioEndpoint)endpoint).getAcceptorThreadPriority();
     }
 
     public int getPollerThreadPriority() {
-      return ((NioEndpoint)getEndpoint()).getThreadPriority();
+      return ((NioEndpoint)endpoint).getThreadPriority();
     }
 
 
     public boolean getUseSendfile() {
-        return getEndpoint().getUseSendfile();
+        return endpoint.getUseSendfile();
     }
 
     public void setUseSendfile(boolean useSendfile) {
-        ((NioEndpoint)getEndpoint()).setUseSendfile(useSendfile);
+        ((NioEndpoint)endpoint).setUseSendfile(useSendfile);
     }
 
     // -------------------- Tcp setup --------------------
     public void setOomParachute(int oomParachute) {
-        ((NioEndpoint)getEndpoint()).setOomParachute(oomParachute);
+        ((NioEndpoint)endpoint).setOomParachute(oomParachute);
     }
 
     // ----------------------------------------------------- JMX related methods
 
     @Override
     protected String getNamePrefix() {
-        if (isSSLEnabled()) {
-            return ("https-nio");
-        } else {
-            return ("http-nio");
-        }
+        return ("http-nio");
     }
 
 
@@ -190,7 +202,7 @@
          * close, errors etc.
          */
         @Override
-        public void release(SocketWrapperBase<NioChannel> socket) {
+        public void release(SocketWrapper<NioChannel> socket) {
             Processor<NioChannel> processor =
                 connections.remove(socket.getSocket());
             if (processor != null) {
@@ -200,7 +212,7 @@
         }
 
         @Override
-        public SocketState process(SocketWrapperBase<NioChannel> socket,
+        public SocketState process(SocketWrapper<NioChannel> socket,
                 SocketStatus status) {
             if (proto.npnHandler != null) {
                 SocketState ss = proto.npnHandler.process(socket, status);
@@ -222,7 +234,7 @@
          * @param addToPoller
          */
         @Override
-        public void release(SocketWrapperBase<NioChannel> socket,
+        public void release(SocketWrapper<NioChannel> socket,
                 Processor<NioChannel> processor, boolean isSocketClosing,
                 boolean addToPoller) {
             processor.recycle(isSocketClosing);
@@ -234,7 +246,7 @@
 
 
         @Override
-        protected void initSsl(SocketWrapperBase<NioChannel> socket,
+        protected void initSsl(SocketWrapper<NioChannel> socket,
                 Processor<NioChannel> processor) {
             if (proto.isSSLEnabled() &&
                     (proto.sslImplementation != null)
@@ -250,13 +262,14 @@
         }
 
         @Override
-        protected void longPoll(SocketWrapperBase<NioChannel> socket,
+        protected void longPoll(SocketWrapper<NioChannel> socket,
                 Processor<NioChannel> processor) {
 
             if (processor.isAsync()) {
                 socket.setAsync(true);
             } else {
                 // Either:
+                //  - this is comet request
                 //  - this is an upgraded connection
                 //  - the request line/headers have not been completely
                 //    read
@@ -267,7 +280,7 @@
         @Override
         public Http11NioProcessor createProcessor() {
             Http11NioProcessor processor = new Http11NioProcessor(
-                    proto.getMaxHttpHeaderSize(), (NioEndpoint)proto.getEndpoint(),
+                    proto.getMaxHttpHeaderSize(), (NioEndpoint)proto.endpoint,
                     proto.getMaxTrailerSize(), proto.getMaxExtensionSize(),
                     proto.getMaxSwallowSize());
             proto.configureProcessor(processor);
@@ -277,10 +290,11 @@
 
         @Override
         protected Processor<NioChannel> createUpgradeProcessor(
-                SocketWrapperBase<NioChannel> socket, ByteBuffer leftoverInput,
+                SocketWrapper<NioChannel> socket, ByteBuffer leftoverInput,
                 HttpUpgradeHandler httpUpgradeProcessor)
                 throws IOException {
-            return new UpgradeProcessor<>(socket, leftoverInput, httpUpgradeProcessor,
+            return new NioProcessor(socket, leftoverInput, httpUpgradeProcessor,
+                    proto.getEndpoint().getSelectorPool(),
                     proto.getUpgradeAsyncWriteBufferSize());
         }
 
diff --git a/java/org/apache/coyote/http11/Http11Processor.java b/java/org/apache/coyote/http11/Http11Processor.java
new file mode 100644
index 0000000..98f8dba
--- /dev/null
+++ b/java/org/apache/coyote/http11/Http11Processor.java
@@ -0,0 +1,412 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.Socket;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.http11.filters.BufferedInputFilter;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.SSLSupport;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Processes HTTP requests.
+ *
+ * @author Remy Maucherat
+ */
+public class Http11Processor extends AbstractHttp11Processor<Socket> {
+
+    private static final Log log = LogFactory.getLog(Http11Processor.class);
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+
+   // ------------------------------------------------------------ Constructor
+
+
+    public Http11Processor(int headerBufferSize, JIoEndpoint endpoint,
+            int maxTrailerSize, int maxExtensionSize, int maxSwallowSize) {
+
+        super(endpoint);
+
+        inputBuffer = new InternalInputBuffer(request, headerBufferSize);
+        request.setInputBuffer(inputBuffer);
+
+        outputBuffer = new InternalOutputBuffer(response, headerBufferSize);
+        response.setOutputBuffer(outputBuffer);
+
+        initializeFilters(maxTrailerSize, maxExtensionSize, maxSwallowSize);
+    }
+
+
+    // ----------------------------------------------------- Instance Variables
+
+    /**
+     * SSL information.
+     */
+    protected SSLSupport sslSupport;
+
+
+    /**
+     * The percentage of threads that have to be in use before keep-alive is
+     * disabled to aid scalability.
+     */
+    private int disableKeepAlivePercentage = 75;
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Set the SSL information for this HTTP connection.
+     */
+    @Override
+    public void setSslSupport(SSLSupport sslSupport) {
+        this.sslSupport = sslSupport;
+    }
+
+
+    public int getDisableKeepAlivePercentage() {
+        return disableKeepAlivePercentage;
+    }
+
+
+    public void setDisableKeepAlivePercentage(int disableKeepAlivePercentage) {
+        this.disableKeepAlivePercentage = disableKeepAlivePercentage;
+    }
+
+
+    @Override
+    protected boolean disableKeepAlive() {
+        int threadRatio = -1;
+        // These may return zero or negative values
+        // Only calculate a thread ratio when both are >0 to ensure we get a
+        // sensible result
+        int maxThreads, threadsBusy;
+        if ((maxThreads = endpoint.getMaxThreads()) > 0
+                && (threadsBusy = endpoint.getCurrentThreadsBusy()) > 0) {
+            threadRatio = (threadsBusy * 100) / maxThreads;
+        }
+        // Disable keep-alive if we are running low on threads
+        if (threadRatio > getDisableKeepAlivePercentage()) {
+            return true;
+        }
+
+        return false;
+    }
+
+
+    @Override
+    protected void setRequestLineReadTimeout() throws IOException {
+
+        /*
+         * When there is no data in the buffer and this is not the first
+         * request on this connection and timeouts are being used the
+         * first read for this request may need a different timeout to
+         * take account of time spent waiting for a processing thread.
+         *
+         * This is a little hacky but better than exposing the socket
+         * and the timeout info to the InputBuffer
+         */
+        if (inputBuffer.lastValid == 0 && socketWrapper.getLastAccess() > -1) {
+            int firstReadTimeout;
+            if (keepAliveTimeout == -1) {
+                firstReadTimeout = 0;
+            } else {
+                long queueTime =
+                    System.currentTimeMillis() - socketWrapper.getLastAccess();
+
+                if (queueTime >= keepAliveTimeout) {
+                    // Queued for longer than timeout but there might be
+                    // data so use shortest possible timeout
+                    firstReadTimeout = 1;
+                } else {
+                    // Cast is safe since queueTime must be less than
+                    // keepAliveTimeout which is an int
+                    firstReadTimeout = keepAliveTimeout - (int) queueTime;
+                }
+            }
+            socketWrapper.getSocket().setSoTimeout(firstReadTimeout);
+            // Blocking IO so fill() always blocks
+            if (!inputBuffer.fill(true)) {
+                throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            // Once the first byte has been read, the standard timeout should be
+            // used so restore it here.
+            if (endpoint.getSoTimeout()> 0) {
+                setSocketTimeout(endpoint.getSoTimeout());
+            } else {
+                setSocketTimeout(0);
+            }
+        }
+    }
+
+
+    @Override
+    protected boolean handleIncompleteRequestLineRead() {
+        // Not used with BIO since it uses blocking reads
+        return false;
+    }
+
+
+    @Override
+    protected void setSocketTimeout(int timeout) throws IOException {
+        socketWrapper.getSocket().setSoTimeout(timeout);
+    }
+
+
+    @Override
+    protected void setCometTimeouts(SocketWrapper<Socket> socketWrapper) {
+        // NO-OP for BIO
+    }
+
+
+    @Override
+    protected boolean breakKeepAliveLoop(SocketWrapper<Socket> socketWrapper) {
+        openSocket = keepAlive;
+        // If we don't have a pipe-lined request allow this thread to be
+        // used by another connection
+        if (inputBuffer.lastValid == 0) {
+            return true;
+        }
+        return false;
+    }
+
+
+    @Override
+    protected void registerForEvent(boolean read, boolean write) {
+        // NO-OP for BIO
+    }
+
+    @Override
+    protected void resetTimeouts() {
+        // NO-OP for BIO
+    }
+
+
+    @Override
+    protected void recycleInternal() {
+        // Recycle
+        this.socketWrapper = null;
+        // Recycle ssl info
+        sslSupport = null;
+    }
+
+
+    @Override
+    public SocketState event(SocketStatus status) throws IOException {
+        // Should never reach this code but in case we do...
+        throw new IOException(
+                sm.getString("http11processor.comet.notsupported"));
+    }
+
+    // ----------------------------------------------------- ActionHook Methods
+
+
+    /**
+     * Send an action to the connector.
+     *
+     * @param actionCode Type of the action
+     * @param param Action parameter
+     */
+    @SuppressWarnings("incomplete-switch") // Other cases are handled by action()
+    @Override
+    public void actionInternal(ActionCode actionCode, Object param) {
+
+        switch (actionCode) {
+        case REQ_SSL_ATTRIBUTE: {
+            try {
+                if (sslSupport != null) {
+                    Object sslO = sslSupport.getCipherSuite();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.CIPHER_SUITE_KEY, sslO);
+                    sslO = sslSupport.getPeerCertificateChain(false);
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.CERTIFICATE_KEY, sslO);
+                    sslO = sslSupport.getKeySize();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.KEY_SIZE_KEY, sslO);
+                    sslO = sslSupport.getSessionId();
+                    if (sslO != null)
+                        request.setAttribute
+                            (SSLSupport.SESSION_ID_KEY, sslO);
+                    request.setAttribute(SSLSupport.SESSION_MGR, sslSupport);
+                }
+            } catch (Exception e) {
+                log.warn(sm.getString("http11processor.socket.ssl"), e);
+            }
+            break;
+        }
+        case REQ_HOST_ADDR_ATTRIBUTE: {
+            if (socketWrapper == null) {
+                request.remoteAddr().recycle();
+            } else {
+                if (socketWrapper.getRemoteAddr() == null) {
+                    InetAddress inetAddr = socketWrapper.getSocket().getInetAddress();
+                    if (inetAddr != null) {
+                        socketWrapper.setRemoteAddr(inetAddr.getHostAddress());
+                    }
+                }
+                request.remoteAddr().setString(socketWrapper.getRemoteAddr());
+            }
+            break;
+        }
+        case REQ_LOCAL_NAME_ATTRIBUTE: {
+            if (socketWrapper == null) {
+                request.localName().recycle();
+            } else {
+                if (socketWrapper.getLocalName() == null) {
+                    InetAddress inetAddr = socketWrapper.getSocket().getLocalAddress();
+                    if (inetAddr != null) {
+                        socketWrapper.setLocalName(inetAddr.getHostName());
+                    }
+                }
+                request.localName().setString(socketWrapper.getLocalName());
+            }
+            break;
+        }
+        case REQ_HOST_ATTRIBUTE: {
+            if (socketWrapper == null) {
+                request.remoteHost().recycle();
+            } else {
+                if (socketWrapper.getRemoteHost() == null) {
+                    InetAddress inetAddr = socketWrapper.getSocket().getInetAddress();
+                    if (inetAddr != null) {
+                        socketWrapper.setRemoteHost(inetAddr.getHostName());
+                    }
+                    if (socketWrapper.getRemoteHost() == null) {
+                        if (socketWrapper.getRemoteAddr() == null &&
+                                inetAddr != null) {
+                            socketWrapper.setRemoteAddr(inetAddr.getHostAddress());
+                        }
+                        if (socketWrapper.getRemoteAddr() != null) {
+                            socketWrapper.setRemoteHost(socketWrapper.getRemoteAddr());
+                        }
+                    }
+                }
+                request.remoteHost().setString(socketWrapper.getRemoteHost());
+            }
+            break;
+        }
+        case REQ_LOCAL_ADDR_ATTRIBUTE: {
+            if (socketWrapper == null) {
+                request.localAddr().recycle();
+            } else {
+                if (socketWrapper.getLocalAddr() == null) {
+                    socketWrapper.setLocalAddr(
+                            socketWrapper.getSocket().getLocalAddress().getHostAddress());
+                }
+                request.localAddr().setString(socketWrapper.getLocalAddr());
+            }
+            break;
+        }
+        case REQ_REMOTEPORT_ATTRIBUTE: {
+            if (socketWrapper == null) {
+                request.setRemotePort(0);
+            } else {
+                if (socketWrapper.getRemotePort() == -1) {
+                    socketWrapper.setRemotePort(socketWrapper.getSocket().getPort());
+                }
+                request.setRemotePort(socketWrapper.getRemotePort());
+            }
+            break;
+        }
+        case REQ_LOCALPORT_ATTRIBUTE: {
+            if (socketWrapper == null) {
+                request.setLocalPort(0);
+            } else {
+                if (socketWrapper.getLocalPort() == -1) {
+                    socketWrapper.setLocalPort(socketWrapper.getSocket().getLocalPort());
+                }
+                request.setLocalPort(socketWrapper.getLocalPort());
+            }
+            break;
+        }
+        case REQ_SSL_CERTIFICATE: {
+            if (sslSupport != null) {
+                /*
+                 * Consume and buffer the request body, so that it does not
+                 * interfere with the client's handshake messages
+                 */
+                InputFilter[] inputFilters = inputBuffer.getFilters();
+                ((BufferedInputFilter) inputFilters[Constants.BUFFERED_FILTER])
+                    .setLimit(maxSavePostSize);
+                inputBuffer.addActiveFilter
+                    (inputFilters[Constants.BUFFERED_FILTER]);
+                try {
+                    Object sslO = sslSupport.getPeerCertificateChain(true);
+                    if( sslO != null) {
+                        request.setAttribute
+                            (SSLSupport.CERTIFICATE_KEY, sslO);
+                    }
+                } catch (Exception e) {
+                    log.warn(sm.getString("http11processor.socket.ssl"), e);
+                }
+            }
+            break;
+        }
+        }
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    @Override
+    protected void prepareRequestInternal() {
+        // NOOP for BIO
+    }
+
+    @Override
+    protected boolean prepareSendfile(OutputFilter[] outputFilters) {
+        // Should never, ever call this code
+        Exception e = new Exception();
+        log.error(sm.getString("http11processor.neverused"), e);
+        return false;
+    }
+
+    @Override
+    protected AbstractInputBuffer<Socket> getInputBuffer() {
+        return inputBuffer;
+    }
+
+    @Override
+    protected AbstractOutputBuffer<Socket> getOutputBuffer() {
+        return outputBuffer;
+    }
+
+    /**
+     * Set the socket buffer flag.
+     */
+    @Override
+    public void setSocketBuffer(int socketBuffer) {
+        super.setSocketBuffer(socketBuffer);
+        outputBuffer.setSocketBuffer(socketBuffer);
+    }
+}
diff --git a/java/org/apache/coyote/http11/Http11Protocol.java b/java/org/apache/coyote/http11/Http11Protocol.java
new file mode 100644
index 0000000..b1dbe47
--- /dev/null
+++ b/java/org/apache/coyote/http11/Http11Protocol.java
@@ -0,0 +1,212 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+import javax.servlet.http.HttpUpgradeHandler;
+
+import org.apache.coyote.AbstractProtocol;
+import org.apache.coyote.Processor;
+import org.apache.coyote.http11.upgrade.BioProcessor;
+import org.apache.juli.logging.Log;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint;
+import org.apache.tomcat.util.net.JIoEndpoint.Handler;
+import org.apache.tomcat.util.net.SSLImplementation;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+
+/**
+ * Abstract the protocol implementation, including threading, etc.
+ * Processor is single threaded and specific to stream-based protocols,
+ * will not fit Jk protocols like JNI.
+ *
+ * @author Remy Maucherat
+ * @author Costin Manolache
+ */
+public class Http11Protocol extends AbstractHttp11JsseProtocol<Socket> {
+
+
+    private static final org.apache.juli.logging.Log log
+        = org.apache.juli.logging.LogFactory.getLog(Http11Protocol.class);
+
+    @Override
+    protected Log getLog() { return log; }
+
+
+    @Override
+    protected AbstractEndpoint.Handler getHandler() {
+        return cHandler;
+    }
+
+
+    // ------------------------------------------------------------ Constructor
+
+
+    public Http11Protocol() {
+        endpoint = new JIoEndpoint();
+        cHandler = new Http11ConnectionHandler(this);
+        ((JIoEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
+        setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
+    }
+
+
+    // ----------------------------------------------------------------- Fields
+
+    private final Http11ConnectionHandler cHandler;
+
+
+    // ------------------------------------------------ HTTP specific properties
+    // ------------------------------------------ managed in the ProtocolHandler
+
+    private int disableKeepAlivePercentage = 75;
+    public int getDisableKeepAlivePercentage() {
+        return disableKeepAlivePercentage;
+    }
+    public void setDisableKeepAlivePercentage(int disableKeepAlivePercentage) {
+        if (disableKeepAlivePercentage < 0) {
+            this.disableKeepAlivePercentage = 0;
+        } else if (disableKeepAlivePercentage > 100) {
+            this.disableKeepAlivePercentage = 100;
+        } else {
+            this.disableKeepAlivePercentage = disableKeepAlivePercentage;
+        }
+    }
+
+    @Override
+    public void start() throws Exception {
+        super.start();
+        if (npnHandler != null) {
+            npnHandler.init(endpoint, 0, getAdapter());
+        }
+    }
+
+    // ----------------------------------------------------- JMX related methods
+
+    @Override
+    protected String getNamePrefix() {
+        return ("http-bio");
+    }
+
+
+    // -----------------------------------  Http11ConnectionHandler Inner Class
+
+    protected static class Http11ConnectionHandler
+            extends AbstractConnectionHandler<Socket, Http11Processor> implements Handler {
+
+        protected Http11Protocol proto;
+
+        Http11ConnectionHandler(Http11Protocol proto) {
+            this.proto = proto;
+        }
+
+        @Override
+        protected AbstractProtocol<Socket> getProtocol() {
+            return proto;
+        }
+
+        @Override
+        protected Log getLog() {
+            return log;
+        }
+
+        @Override
+        public SSLImplementation getSslImplementation() {
+            return proto.sslImplementation;
+        }
+
+        @Override
+        public SocketState process(SocketWrapper<Socket> socket,
+                SocketStatus status) {
+            if (proto.npnHandler != null) {
+                SocketState ss = proto.npnHandler.process(socket, status);
+                if (ss != SocketState.OPEN) {
+                    return ss;
+                }
+            }
+            return super.process(socket, status);
+        }
+
+        /**
+         * Expected to be used by the handler once the processor is no longer
+         * required.
+         *
+         * @param socket            Not used in BIO
+         * @param processor
+         * @param isSocketClosing   Not used in HTTP
+         * @param addToPoller       Not used in BIO
+         */
+        @Override
+        public void release(SocketWrapper<Socket> socket,
+                Processor<Socket> processor, boolean isSocketClosing,
+                boolean addToPoller) {
+            processor.recycle(isSocketClosing);
+            recycledProcessors.push(processor);
+        }
+
+        @Override
+        protected void initSsl(SocketWrapper<Socket> socket,
+                Processor<Socket> processor) {
+            if (proto.isSSLEnabled() && (proto.sslImplementation != null)) {
+                processor.setSslSupport(
+                        proto.sslImplementation.getSSLSupport(
+                                socket.getSocket()));
+            } else {
+                processor.setSslSupport(null);
+            }
+
+        }
+
+        @Override
+        protected void longPoll(SocketWrapper<Socket> socket,
+                Processor<Socket> processor) {
+            // NO-OP
+        }
+
+        @Override
+        protected Http11Processor createProcessor() {
+            Http11Processor processor = new Http11Processor(
+                    proto.getMaxHttpHeaderSize(), (JIoEndpoint)proto.endpoint,
+                    proto.getMaxTrailerSize(),proto.getMaxExtensionSize(),
+                    proto.getMaxSwallowSize());
+            proto.configureProcessor(processor);
+            // BIO specific configuration
+            processor.setDisableKeepAlivePercentage(proto.getDisableKeepAlivePercentage());
+            register(processor);
+            return processor;
+        }
+
+        @Override
+        protected Processor<Socket> createUpgradeProcessor(
+                SocketWrapper<Socket> socket, ByteBuffer leftoverInput,
+                HttpUpgradeHandler httpUpgradeProcessor)
+                throws IOException {
+            return new BioProcessor(socket, leftoverInput, httpUpgradeProcessor,
+                    proto.getUpgradeAsyncWriteBufferSize());
+        }
+
+        @Override
+        public void beforeHandshake(SocketWrapper<Socket> socket) {
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/InternalAprInputBuffer.java b/java/org/apache/coyote/http11/InternalAprInputBuffer.java
index f9f20d9..cf8ab1c 100644
--- a/java/org/apache/coyote/http11/InternalAprInputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalAprInputBuffer.java
@@ -33,7 +33,7 @@
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
 import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Implementation of InputBuffer which provides HTTP request header parsing as
@@ -91,7 +91,7 @@
     private long socket;
 
 
-    private SocketWrapperBase<Long> wrapper;
+    private SocketWrapper<Long> wrapper;
 
 
     // --------------------------------------------------------- Public Methods
@@ -547,7 +547,7 @@
     // ------------------------------------------------------ Protected Methods
 
     @Override
-    protected void init(SocketWrapperBase<Long> socketWrapper,
+    protected void init(SocketWrapper<Long> socketWrapper,
             AbstractEndpoint<Long> endpoint) throws IOException {
 
         socket = socketWrapper.getSocket().longValue();
diff --git a/java/org/apache/coyote/http11/InternalAprOutputBuffer.java b/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
index fb10583..73a323e 100644
--- a/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalAprOutputBuffer.java
@@ -24,12 +24,14 @@
 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
 
 import org.apache.coyote.ByteBufferHolder;
+import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Response;
 import org.apache.tomcat.jni.Socket;
 import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AprEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Output buffer.
@@ -48,10 +50,12 @@
         super(response, headerBufferSize);
 
         if (headerBufferSize < (8 * 1024)) {
-            socketWriteBuffer = ByteBuffer.allocateDirect(6 * 1500);
+            bbuf = ByteBuffer.allocateDirect(6 * 1500);
         } else {
-            socketWriteBuffer = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);
+            bbuf = ByteBuffer.allocateDirect((headerBufferSize / 1500 + 1) * 1500);
         }
+
+        outputStreamOutputBuffer = new SocketOutputBuffer();
     }
 
 
@@ -64,7 +68,20 @@
     private long socket;
 
 
-    private SocketWrapperBase<Long> wrapper;
+    private SocketWrapper<Long> wrapper;
+
+
+    /**
+     * Direct byte buffer used for writing.
+     */
+    private final ByteBuffer bbuf;
+
+
+    /**
+     * <code>false</code> if bbuf is ready to be written to and
+     * <code>true</code> is ready to be read from.
+     */
+    private volatile boolean flipped = false;
 
 
     private AbstractEndpoint<Long> endpoint;
@@ -73,14 +90,14 @@
     // --------------------------------------------------------- Public Methods
 
     @Override
-    public void init(SocketWrapperBase<Long> socketWrapper,
+    public void init(SocketWrapper<Long> socketWrapper,
             AbstractEndpoint<Long> endpoint) throws IOException {
 
         wrapper = socketWrapper;
         socket = socketWrapper.getSocket().longValue();
         this.endpoint = endpoint;
 
-        Socket.setsbb(this.socket, socketWriteBuffer);
+        Socket.setsbb(this.socket, bbuf);
     }
 
 
@@ -90,8 +107,12 @@
      */
     @Override
     public void recycle() {
+
         super.recycle();
-        socketWriteBuffer.clear();
+
+        bbuf.clear();
+        flipped = false;
+
         socket = 0;
         wrapper = null;
     }
@@ -113,15 +134,35 @@
 
     // ------------------------------------------------------ Protected Methods
 
+
+    /**
+     * Commit the response.
+     *
+     * @throws IOException an underlying I/O error occurred
+     */
     @Override
-    protected synchronized void addToBB(byte[] buf, int offset, int length)
+    protected void commit() throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            bbuf.put(headerBuffer, 0, pos);
+        }
+
+    }
+
+
+    private synchronized void addToBB(byte[] buf, int offset, int length)
             throws IOException {
 
         if (length == 0) return;
 
         // If bbuf is currently being used for writes, add this data to the
         // write buffer
-        if (writeBufferFlipped) {
+        if (flipped) {
             addToBuffers(buf, offset, length);
             return;
         }
@@ -130,19 +171,21 @@
         // leaves data in the buffer
         while (length > 0) {
             int thisTime = length;
-            if (socketWriteBuffer.position() == socketWriteBuffer.capacity()) {
+            if (bbuf.position() == bbuf.capacity()) {
                 if (flushBuffer(isBlocking())) {
                     break;
                 }
             }
-            if (thisTime > socketWriteBuffer.capacity() - socketWriteBuffer.position()) {
-                thisTime = socketWriteBuffer.capacity() - socketWriteBuffer.position();
+            if (thisTime > bbuf.capacity() - bbuf.position()) {
+                thisTime = bbuf.capacity() - bbuf.position();
             }
-            socketWriteBuffer.put(buf, offset, thisTime);
+            bbuf.put(buf, offset, thisTime);
             length = length - thisTime;
             offset = offset + thisTime;
         }
 
+        wrapper.access();
+
         if (!isBlocking() && length>0) {
             // Buffer the remaining data
             addToBuffers(buf, offset, length);
@@ -165,6 +208,8 @@
     protected synchronized boolean flushBuffer(boolean block)
             throws IOException {
 
+        wrapper.access();
+
         if (hasMoreDataToFlush()) {
             writeToSocket(block);
         }
@@ -175,7 +220,7 @@
                 ByteBufferHolder buffer = bufIter.next();
                 buffer.flip();
                 while (!hasMoreDataToFlush() && buffer.getBuf().remaining()>0) {
-                    transfer(buffer.getBuf(), socketWriteBuffer);
+                    transfer(buffer.getBuf(), bbuf);
                     if (buffer.getBuf().remaining() == 0) {
                         bufIter.remove();
                     }
@@ -232,26 +277,26 @@
     }
 
     private synchronized void writeToSocket() throws IOException {
-        if (!writeBufferFlipped) {
-            writeBufferFlipped = true;
-            socketWriteBuffer.flip();
+        if (!flipped) {
+            flipped = true;
+            bbuf.flip();
         }
 
         int written;
 
         do {
-            written = Socket.sendbb(socket, socketWriteBuffer.position(), socketWriteBuffer.remaining());
+            written = Socket.sendbb(socket, bbuf.position(), bbuf.remaining());
             if (Status.APR_STATUS_IS_EAGAIN(-written)) {
                 written = 0;
             } else if (written < 0) {
                 throw new IOException("APR error: " + written);
             }
-            socketWriteBuffer.position(socketWriteBuffer.position() + written);
-        } while (written > 0 && socketWriteBuffer.hasRemaining());
+            bbuf.position(bbuf.position() + written);
+        } while (written > 0 && bbuf.hasRemaining());
 
-        if (socketWriteBuffer.remaining() == 0) {
-            socketWriteBuffer.clear();
-            writeBufferFlipped = false;
+        if (bbuf.remaining() == 0) {
+            bbuf.clear();
+            flipped = false;
         }
         // If there is data left in the buffer the socket will be registered for
         // write further up the stack. This is to ensure the socket is only
@@ -260,11 +305,21 @@
     }
 
 
+    private void transfer(ByteBuffer from, ByteBuffer to) {
+        int max = Math.min(from.remaining(), to.remaining());
+        int fromLimit = from.limit();
+        from.limit(from.position() + max);
+        to.put(from);
+        from.limit(fromLimit);
+    }
+
+
     //-------------------------------------------------- Non-blocking IO methods
 
     @Override
     protected synchronized boolean hasMoreDataToFlush() {
-        return super.hasMoreDataToFlush();
+        return (flipped && bbuf.remaining() > 0) ||
+                (!flipped && bbuf.position() > 0);
     }
 
 
@@ -272,4 +327,34 @@
     protected void registerWriteInterest() {
         ((AprEndpoint) endpoint).getPoller().add(socket, -1, false, true);
     }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) throws IOException {
+
+            int len = chunk.getLength();
+            int start = chunk.getStart();
+            byte[] b = chunk.getBuffer();
+            addToBB(b, start, len);
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
 }
diff --git a/java/org/apache/coyote/http11/InternalInputBuffer.java b/java/org/apache/coyote/http11/InternalInputBuffer.java
new file mode 100644
index 0000000..cca628f
--- /dev/null
+++ b/java/org/apache/coyote/http11/InternalInputBuffer.java
@@ -0,0 +1,592 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.Request;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+/**
+ * Implementation of InputBuffer which provides HTTP request header parsing as
+ * well as transfer decoding.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalInputBuffer extends AbstractInputBuffer<Socket> {
+
+    private static final Log log = LogFactory.getLog(InternalInputBuffer.class);
+
+
+    /**
+     * Underlying input stream.
+     */
+    private InputStream inputStream;
+
+
+    /**
+     * Default constructor.
+     */
+    public InternalInputBuffer(Request request, int headerBufferSize) {
+
+        this.request = request;
+        headers = request.getMimeHeaders();
+
+        buf = new byte[headerBufferSize];
+
+        inputStreamInputBuffer = new InputStreamInputBuffer();
+
+        filterLibrary = new InputFilter[0];
+        activeFilters = new InputFilter[0];
+        lastActiveFilter = -1;
+
+        parsingHeader = true;
+        swallowInput = true;
+
+    }
+
+
+    /**
+     * Data is always available for blocking IO (if you wait long enough) so
+     * return a value of 1. Note that the actual value is never used it is only
+     * tested for == 0 or &gt; 0.
+     */
+    @Override
+    public int available() {
+        return 1;
+    }
+
+
+    /**
+     * Read the request line. This function is meant to be used during the
+     * HTTP request header parsing. Do NOT attempt to read the request body
+     * using it.
+     *
+     * @throws IOException If an exception occurs during the underlying socket
+     * read operations, or if the given buffer is not big enough to accommodate
+     * the whole line.
+     */
+    @Override
+    public boolean parseRequestLine(boolean useAvailableDataOnly)
+
+        throws IOException {
+
+        int start = 0;
+
+        //
+        // Skipping blank lines
+        //
+
+        byte chr = 0;
+        do {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            // Set the start time once we start reading data (even if it is
+            // just skipping blank lines)
+            if (request.getStartTime() < 0) {
+                request.setStartTime(System.currentTimeMillis());
+            }
+            chr = buf[pos++];
+        } while ((chr == Constants.CR) || (chr == Constants.LF));
+
+        pos--;
+
+        // Mark the current buffer position
+        start = pos;
+
+        //
+        // Reading the method name
+        // Method name is always US-ASCII
+        //
+
+        boolean space = false;
+
+        while (!space) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            // Spec says no CR or LF in method name
+            if (buf[pos] == Constants.CR || buf[pos] == Constants.LF) {
+                throw new IllegalArgumentException(
+                        sm.getString("iib.invalidmethod"));
+            }
+            // Spec says single SP but it also says be tolerant of HT
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                space = true;
+                request.method().setBytes(buf, start, pos - start);
+            }
+
+            pos++;
+
+        }
+
+
+        // Spec says single SP but also says be tolerant of multiple and/or HT
+        while (space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                pos++;
+            } else {
+                space = false;
+            }
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int end = 0;
+        int questionPos = -1;
+
+        //
+        // Reading the URI
+        //
+
+        boolean eol = false;
+
+        while (!space) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            // Spec says single SP but it also says be tolerant of HT
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == Constants.CR)
+                       || (buf[pos] == Constants.LF)) {
+                // HTTP/0.9 style request
+                eol = true;
+                space = true;
+                end = pos;
+            } else if ((buf[pos] == Constants.QUESTION)
+                       && (questionPos == -1)) {
+                questionPos = pos;
+            }
+
+            pos++;
+
+        }
+
+        if (questionPos >= 0) {
+            request.queryString().setBytes(buf, questionPos + 1,
+                                           end - questionPos - 1);
+            request.requestURI().setBytes(buf, start, questionPos - start);
+        } else {
+            request.requestURI().setBytes(buf, start, end - start);
+        }
+
+        // Spec says single SP but also says be tolerant of multiple and/or HT
+        while (space) {
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+            if (buf[pos] == Constants.SP || buf[pos] == Constants.HT) {
+                pos++;
+            } else {
+                space = false;
+            }
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        end = 0;
+
+        //
+        // Reading the protocol
+        // Protocol is always US-ASCII
+        //
+
+        while (!eol) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.CR) {
+                end = pos;
+            } else if (buf[pos] == Constants.LF) {
+                if (end == 0)
+                    end = pos;
+                eol = true;
+            }
+
+            pos++;
+
+        }
+
+        if ((end - start) > 0) {
+            request.protocol().setBytes(buf, start, end - start);
+        } else {
+            request.protocol().setString("");
+        }
+
+        return true;
+
+    }
+
+
+    /**
+     * Parse the HTTP headers.
+     */
+    @Override
+    public boolean parseHeaders()
+        throws IOException {
+        if (!parsingHeader) {
+            throw new IllegalStateException(
+                    sm.getString("iib.parseheaders.ise.error"));
+        }
+
+        while (parseHeader()) {
+            // Loop until we run out of headers
+        }
+
+        parsingHeader = false;
+        end = pos;
+        return true;
+    }
+
+
+    /**
+     * Parse an HTTP header.
+     *
+     * @return false after reading a blank line (which indicates that the
+     * HTTP header parsing is done
+     */
+    @SuppressWarnings("null") // headerValue cannot be null
+    private boolean parseHeader()
+        throws IOException {
+
+        //
+        // Check for blank line
+        //
+
+        byte chr = 0;
+        while (true) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos];
+
+            if (chr == Constants.CR) {
+                // Skip
+            } else if (chr == Constants.LF) {
+                pos++;
+                return false;
+            } else {
+                break;
+            }
+
+            pos++;
+
+        }
+
+        // Mark the current buffer position
+        int start = pos;
+
+        //
+        // Reading the header name
+        // Header name is always US-ASCII
+        //
+
+        boolean colon = false;
+        MessageBytes headerValue = null;
+
+        while (!colon) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.COLON) {
+                colon = true;
+                headerValue = headers.addValue(buf, start, pos - start);
+            } else if (!HTTP_TOKEN_CHAR[buf[pos]]) {
+                // If a non-token header is detected, skip the line and
+                // ignore the header
+                skipLine(start);
+                return true;
+            }
+
+            chr = buf[pos];
+            if ((chr >= Constants.A) && (chr <= Constants.Z)) {
+                buf[pos] = (byte) (chr - Constants.LC_OFFSET);
+            }
+
+            pos++;
+
+        }
+
+        // Mark the current buffer position
+        start = pos;
+        int realPos = pos;
+
+        //
+        // Reading the header value (which can be spanned over multiple lines)
+        //
+
+        boolean eol = false;
+        boolean validLine = true;
+
+        while (validLine) {
+
+            boolean space = true;
+
+            // Skipping spaces
+            while (space) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill())
+                        throw new EOFException(sm.getString("iib.eof.error"));
+                }
+
+                if ((buf[pos] == Constants.SP) || (buf[pos] == Constants.HT)) {
+                    pos++;
+                } else {
+                    space = false;
+                }
+
+            }
+
+            int lastSignificantChar = realPos;
+
+            // Reading bytes until the end of the line
+            while (!eol) {
+
+                // Read new bytes if needed
+                if (pos >= lastValid) {
+                    if (!fill())
+                        throw new EOFException(sm.getString("iib.eof.error"));
+                }
+
+                if (buf[pos] == Constants.CR) {
+                    // Skip
+                } else if (buf[pos] == Constants.LF) {
+                    eol = true;
+                } else if (buf[pos] == Constants.SP) {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                } else {
+                    buf[realPos] = buf[pos];
+                    realPos++;
+                    lastSignificantChar = realPos;
+                }
+
+                pos++;
+
+            }
+
+            realPos = lastSignificantChar;
+
+            // Checking the first character of the new line. If the character
+            // is a LWS, then it's a multiline header
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            chr = buf[pos];
+            if ((chr != Constants.SP) && (chr != Constants.HT)) {
+                validLine = false;
+            } else {
+                eol = false;
+                // Copying one extra space in the buffer (since there must
+                // be at least one space inserted between the lines)
+                buf[realPos] = chr;
+                realPos++;
+            }
+
+        }
+
+        // Set the header value
+        headerValue.setBytes(buf, start, realPos - start);
+
+        return true;
+
+    }
+
+
+    @Override
+    public void recycle() {
+        super.recycle();
+        inputStream = null;
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    @Override
+    protected void init(SocketWrapper<Socket> socketWrapper,
+            AbstractEndpoint<Socket> endpoint) throws IOException {
+        inputStream = socketWrapper.getSocket().getInputStream();
+    }
+
+
+
+    private void skipLine(int start) throws IOException {
+        boolean eol = false;
+        int lastRealByte = start;
+        if (pos - 1 > start) {
+            lastRealByte = pos - 1;
+        }
+
+        while (!eol) {
+
+            // Read new bytes if needed
+            if (pos >= lastValid) {
+                if (!fill())
+                    throw new EOFException(sm.getString("iib.eof.error"));
+            }
+
+            if (buf[pos] == Constants.CR) {
+                // Skip
+            } else if (buf[pos] == Constants.LF) {
+                eol = true;
+            } else {
+                lastRealByte = pos;
+            }
+            pos++;
+        }
+
+        if (log.isDebugEnabled()) {
+            log.debug(sm.getString("iib.invalidheader", new String(buf, start,
+                    lastRealByte - start + 1, StandardCharsets.ISO_8859_1)));
+        }
+    }
+
+    /**
+     * Fill the internal buffer using data from the underlying input stream.
+     *
+     * @return false if at end of stream
+     */
+    protected boolean fill() throws IOException {
+        return fill(true);
+    }
+
+    @Override
+    protected boolean fill(boolean block) throws IOException {
+
+        int nRead = 0;
+
+        if (parsingHeader) {
+
+            if (lastValid == buf.length) {
+                throw new IllegalArgumentException
+                    (sm.getString("iib.requestheadertoolarge.error"));
+            }
+
+            nRead = inputStream.read(buf, pos, buf.length - lastValid);
+            if (nRead > 0) {
+                lastValid = pos + nRead;
+            }
+
+        } else {
+
+            if (buf.length - end < 4500) {
+                // In this case, the request header was really large, so we allocate a
+                // brand new one; the old one will get GCed when subsequent requests
+                // clear all references
+                buf = new byte[buf.length];
+                end = 0;
+            }
+            pos = end;
+            lastValid = pos;
+            nRead = inputStream.read(buf, pos, buf.length - lastValid);
+            if (nRead > 0) {
+                lastValid = pos + nRead;
+            }
+
+        }
+
+        return (nRead > 0);
+
+    }
+
+
+    @Override
+    protected final Log getLog() {
+        return log;
+    }
+
+
+    // ------------------------------------- InputStreamInputBuffer Inner Class
+
+    /**
+     * This class is an input buffer which will read its data from an input
+     * stream.
+     */
+    protected class InputStreamInputBuffer
+        implements InputBuffer {
+
+
+        /**
+         * Read bytes into the specified chunk.
+         */
+        @Override
+        public int doRead(ByteChunk chunk, Request req )
+            throws IOException {
+
+            if (pos >= lastValid) {
+                if (!fill())
+                    return -1;
+            }
+
+            int length = lastValid - pos;
+            chunk.setBytes(buf, pos, length);
+            pos = lastValid;
+
+            return (length);
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/InternalNio2InputBuffer.java b/java/org/apache/coyote/http11/InternalNio2InputBuffer.java
index ae87b3d..3b81d4f 100644
--- a/java/org/apache/coyote/http11/InternalNio2InputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalNio2InputBuffer.java
@@ -36,7 +36,7 @@
 import org.apache.tomcat.util.net.Nio2Channel;
 import org.apache.tomcat.util.net.Nio2Endpoint;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Output buffer implementation for NIO2.
@@ -57,7 +57,7 @@
     /**
      * Underlying socket.
      */
-    private SocketWrapperBase<Nio2Channel> socket;
+    private SocketWrapper<Nio2Channel> socket;
 
     /**
      * Track write interest
@@ -67,7 +67,7 @@
     /**
      * The completion handler used for asynchronous read operations
      */
-    private CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> completionHandler;
+    private CompletionHandler<Integer, SocketWrapper<Nio2Channel>> completionHandler;
 
     /**
      * The associated endpoint.
@@ -131,7 +131,7 @@
     // ------------------------------------------------------ Protected Methods
 
     @Override
-    protected void init(SocketWrapperBase<Nio2Channel> socketWrapper,
+    protected void init(SocketWrapper<Nio2Channel> socketWrapper,
             AbstractEndpoint<Nio2Channel> associatedEndpoint) throws IOException {
 
         endpoint = associatedEndpoint;
@@ -149,10 +149,10 @@
         }
 
         // Initialize the completion handler
-        this.completionHandler = new CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>>() {
+        this.completionHandler = new CompletionHandler<Integer, SocketWrapper<Nio2Channel>>() {
 
             @Override
-            public void completed(Integer nBytes, SocketWrapperBase<Nio2Channel> attachment) {
+            public void completed(Integer nBytes, SocketWrapper<Nio2Channel> attachment) {
                 boolean notify = false;
                 synchronized (completionHandler) {
                     if (nBytes.intValue() < 0) {
@@ -171,7 +171,7 @@
             }
 
             @Override
-            public void failed(Throwable exc, SocketWrapperBase<Nio2Channel> attachment) {
+            public void failed(Throwable exc, SocketWrapper<Nio2Channel> attachment) {
                 attachment.setError(true);
                 if (exc instanceof IOException) {
                     e = (IOException) exc;
diff --git a/java/org/apache/coyote/http11/InternalNio2OutputBuffer.java b/java/org/apache/coyote/http11/InternalNio2OutputBuffer.java
index 6859561..555570b 100644
--- a/java/org/apache/coyote/http11/InternalNio2OutputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalNio2OutputBuffer.java
@@ -30,12 +30,14 @@
 
 import javax.servlet.RequestDispatcher;
 
+import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.Nio2Channel;
 import org.apache.tomcat.util.net.Nio2Endpoint;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Output buffer implementation for NIO2.
@@ -49,6 +51,7 @@
      */
     public InternalNio2OutputBuffer(Response response, int headerBufferSize) {
         super(response, headerBufferSize);
+        outputStreamOutputBuffer = new SocketOutputBuffer();
     }
 
     private static final ByteBuffer[] EMPTY_BUF_ARRAY = new ByteBuffer[0];
@@ -56,7 +59,7 @@
     /**
      * Underlying socket.
      */
-    private SocketWrapperBase<Nio2Channel> socket;
+    private SocketWrapper<Nio2Channel> socket;
 
     /**
      * Track write interest
@@ -64,6 +67,11 @@
     protected volatile boolean interest = false;
 
     /**
+     * Track if the byte buffer is flipped
+     */
+    protected volatile boolean flipped = false;
+
+    /**
      * The completion handler used for asynchronous write operations
      */
     protected CompletionHandler<Integer, ByteBuffer> completionHandler;
@@ -96,11 +104,10 @@
     // --------------------------------------------------------- Public Methods
 
     @Override
-    public void init(SocketWrapperBase<Nio2Channel> socketWrapper,
+    public void init(SocketWrapper<Nio2Channel> socketWrapper,
             AbstractEndpoint<Nio2Channel> associatedEndpoint) throws IOException {
         this.socket = socketWrapper;
         this.endpoint = associatedEndpoint;
-        this.socketWriteBuffer = socket.getSocket().getBufHandler().getWriteBuffer();
 
         this.completionHandler = new CompletionHandler<Integer, ByteBuffer>() {
             @Override
@@ -218,6 +225,7 @@
         super.recycle();
         socket = null;
         e = null;
+        flipped = false;
         interest = false;
         if (writePending.availablePermits() != 1) {
             writePending.drainPermits();
@@ -230,8 +238,8 @@
     @Override
     public void nextRequest() {
         super.nextRequest();
+        flipped = false;
         interest = false;
-        writeBufferFlipped = false;
     }
 
     // ------------------------------------------------ HTTP/1.1 Output Methods
@@ -248,6 +256,25 @@
 
     // ------------------------------------------------------ Protected Methods
 
+    /**
+     * Commit the response.
+     *
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    protected void commit() throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            addToBB(headerBuffer, 0, pos);
+        }
+
+    }
+
     private static boolean arrayHasData(ByteBuffer[] byteBuffers) {
         for (ByteBuffer byteBuffer : byteBuffers) {
             if (byteBuffer.hasRemaining()) {
@@ -257,8 +284,7 @@
         return false;
     }
 
-    @Override
-    protected void addToBB(byte[] buf, int offset, int length)
+    private void addToBB(byte[] buf, int offset, int length)
             throws IOException {
 
         if (length == 0)
@@ -266,12 +292,16 @@
         if (socket == null || socket.getSocket() == null)
             return;
 
+        ByteBuffer writeByteBuffer = socket.getSocket().getBufHandler().getWriteBuffer();
+
+        socket.access();
+
         if (isBlocking()) {
             while (length > 0) {
-                int thisTime = transfer(buf, offset, length, socketWriteBuffer);
+                int thisTime = transfer(buf, offset, length, writeByteBuffer);
                 length = length - thisTime;
                 offset = offset + thisTime;
-                if (socketWriteBuffer.remaining() == 0) {
+                if (writeByteBuffer.remaining() == 0) {
                     flushBuffer(true);
                 }
             }
@@ -288,7 +318,7 @@
                 synchronized (completionHandler) {
                     // No pending completion handler, so writing to the main buffer
                     // is possible
-                    int thisTime = transfer(buf, offset, length, socketWriteBuffer);
+                    int thisTime = transfer(buf, offset, length, writeByteBuffer);
                     length = length - thisTime;
                     offset = offset + thisTime;
                     if (length > 0) {
@@ -313,6 +343,13 @@
     }
 
 
+    private int transfer(byte[] from, int offset, int length, ByteBuffer to) {
+        int max = Math.min(length, to.remaining());
+        to.put(from, offset, max);
+        return max;
+    }
+
+
     /**
      * Callback to write data from the buffer.
      */
@@ -328,6 +365,7 @@
         if (socket == null || socket.getSocket() == null)
             return false;
 
+        ByteBuffer byteBuffer = socket.getSocket().getBufHandler().getWriteBuffer();
         if (block) {
             if (!isBlocking()) {
                 // The final flush is blocking, but the processing was using
@@ -352,12 +390,12 @@
                     }
                     bufferedWrites.clear();
                 }
-                if (!writeBufferFlipped) {
-                    socketWriteBuffer.flip();
-                    writeBufferFlipped = true;
+                if (!flipped) {
+                    byteBuffer.flip();
+                    flipped = true;
                 }
-                while (socketWriteBuffer.hasRemaining()) {
-                    if (socket.getSocket().write(socketWriteBuffer).get(socket.getTimeout(), TimeUnit.MILLISECONDS).intValue() < 0) {
+                while (byteBuffer.hasRemaining()) {
+                    if (socket.getSocket().write(byteBuffer).get(socket.getTimeout(), TimeUnit.MILLISECONDS).intValue() < 0) {
                         throw new EOFException(sm.getString("iob.failedwrite"));
                     }
                 }
@@ -372,22 +410,24 @@
             } catch (TimeoutException e) {
                 throw new SocketTimeoutException();
             }
-            socketWriteBuffer.clear();
-            writeBufferFlipped = false;
+            byteBuffer.clear();
+            flipped = false;
             return false;
         } else {
             synchronized (completionHandler) {
                 if (hasPermit || writePending.tryAcquire()) {
-                    if (!writeBufferFlipped) {
-                        socketWriteBuffer.flip();
-                        writeBufferFlipped = true;
+                    //prevent timeout for async
+                    socket.access();
+                    if (!flipped) {
+                        byteBuffer.flip();
+                        flipped = true;
                     }
                     Nio2Endpoint.startInline();
                     if (bufferedWrites.size() > 0) {
                         // Gathering write of the main buffer plus all leftovers
                         ArrayList<ByteBuffer> arrayList = new ArrayList<>();
-                        if (socketWriteBuffer.hasRemaining()) {
-                            arrayList.add(socketWriteBuffer);
+                        if (byteBuffer.hasRemaining()) {
+                            arrayList.add(byteBuffer);
                         }
                         for (ByteBuffer buffer : bufferedWrites) {
                             buffer.flip();
@@ -397,19 +437,19 @@
                         ByteBuffer[] array = arrayList.toArray(EMPTY_BUF_ARRAY);
                         socket.getSocket().write(array, 0, array.length, socket.getTimeout(),
                                 TimeUnit.MILLISECONDS, array, gatherCompletionHandler);
-                    } else if (socketWriteBuffer.hasRemaining()) {
+                    } else if (byteBuffer.hasRemaining()) {
                         // Regular write
-                        socket.getSocket().write(socketWriteBuffer, socket.getTimeout(),
-                                TimeUnit.MILLISECONDS, socketWriteBuffer, completionHandler);
+                        socket.getSocket().write(byteBuffer, socket.getTimeout(),
+                                TimeUnit.MILLISECONDS, byteBuffer, completionHandler);
                     } else {
                         // Nothing was written
                         writePending.release();
                     }
                     Nio2Endpoint.endInline();
                     if (writePending.availablePermits() > 0) {
-                        if (socketWriteBuffer.remaining() == 0) {
-                            socketWriteBuffer.clear();
-                            writeBufferFlipped = false;
+                        if (byteBuffer.remaining() == 0) {
+                            byteBuffer.clear();
+                            flipped = false;
                         }
                     }
                 }
@@ -427,12 +467,18 @@
     }
 
     @Override
+    protected boolean hasMoreDataToFlush() {
+        return (flipped && socket.getSocket().getBufHandler().getWriteBuffer().remaining() > 0) ||
+                (!flipped && socket.getSocket().getBufHandler().getWriteBuffer().position() > 0);
+    }
+
+    @Override
     protected boolean hasBufferedData() {
         return bufferedWrites.size() > 0;
     }
 
     @Override
-    protected void registerWriteInterest() {
+    public void registerWriteInterest() {
         synchronized (completionHandler) {
             if (writePending.availablePermits() == 0) {
                 interest = true;
@@ -442,4 +488,32 @@
             }
         }
     }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer implements OutputBuffer {
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) throws IOException {
+            int len = chunk.getLength();
+            int start = chunk.getStart();
+            byte[] b = chunk.getBuffer();
+            addToBB(b, start, len);
+            byteCount += len;
+            return len;
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
 }
diff --git a/java/org/apache/coyote/http11/InternalNioInputBuffer.java b/java/org/apache/coyote/http11/InternalNioInputBuffer.java
index fa7d029..f802589 100644
--- a/java/org/apache/coyote/http11/InternalNioInputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalNioInputBuffer.java
@@ -30,7 +30,7 @@
 import org.apache.tomcat.util.net.NioChannel;
 import org.apache.tomcat.util.net.NioEndpoint;
 import org.apache.tomcat.util.net.NioSelectorPool;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Implementation of InputBuffer which provides HTTP request header parsing as
@@ -84,7 +84,7 @@
     // ------------------------------------------------------ Protected Methods
 
     @Override
-    protected void init(SocketWrapperBase<NioChannel> socketWrapper,
+    protected void init(SocketWrapper<NioChannel> socketWrapper,
             AbstractEndpoint<NioChannel> endpoint) throws IOException {
 
         socket = socketWrapper.getSocket();
@@ -125,8 +125,8 @@
                 // Ignore
             }
             try {
-                NioEndpoint.NioSocketWrapper att =
-                        (NioEndpoint.NioSocketWrapper) socket.getAttachment();
+                NioEndpoint.KeyAttachment att =
+                        (NioEndpoint.KeyAttachment) socket.getAttachment();
                 if (att == null) {
                     throw new IOException("Key must be cancelled.");
                 }
diff --git a/java/org/apache/coyote/http11/InternalNioOutputBuffer.java b/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
index 6bb372b..ba06c82 100644
--- a/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
+++ b/java/org/apache/coyote/http11/InternalNioOutputBuffer.java
@@ -24,12 +24,14 @@
 import java.util.Iterator;
 
 import org.apache.coyote.ByteBufferHolder;
+import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.NioChannel;
 import org.apache.tomcat.util.net.NioEndpoint;
 import org.apache.tomcat.util.net.NioSelectorPool;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Output buffer.
@@ -44,7 +46,10 @@
      * Default constructor.
      */
     public InternalNioOutputBuffer(Response response, int headerBufferSize) {
+
         super(response, headerBufferSize);
+
+        outputStreamOutputBuffer = new SocketOutputBuffer();
     }
 
 
@@ -58,16 +63,20 @@
      */
     private NioSelectorPool pool;
 
+    /**
+     * Track if the byte buffer is flipped
+     */
+    protected volatile boolean flipped = false;
+
 
     // --------------------------------------------------------- Public Methods
 
     @Override
-    public void init(SocketWrapperBase<NioChannel> socketWrapper,
+    public void init(SocketWrapper<NioChannel> socketWrapper,
             AbstractEndpoint<NioChannel> endpoint) throws IOException {
 
         socket = socketWrapper.getSocket();
         pool = ((NioEndpoint)endpoint).getSelectorPool();
-        socketWriteBuffer = socket.getBufHandler().getWriteBuffer();
     }
 
 
@@ -78,8 +87,11 @@
     @Override
     public void recycle() {
         super.recycle();
-        socketWriteBuffer.clear();
-        socket = null;
+        if (socket != null) {
+            socket.getBufHandler().getWriteBuffer().clear();
+            socket = null;
+        }
+        flipped = false;
     }
 
 
@@ -91,8 +103,9 @@
     @Override
     public void sendAck() throws IOException {
         if (!committed) {
-            socketWriteBuffer.put(Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length);
-            int result = writeToSocket(socketWriteBuffer, true, true);
+            socket.getBufHandler().getWriteBuffer().put(
+                    Constants.ACK_BYTES, 0, Constants.ACK_BYTES.length);
+            int result = writeToSocket(socket.getBufHandler().getWriteBuffer(), true, true);
             if (result < 0) {
                 throw new IOException(sm.getString("iob.failedwrite.ack"));
             }
@@ -109,11 +122,11 @@
     private synchronized int writeToSocket(ByteBuffer bytebuffer, boolean block, boolean flip) throws IOException {
         if ( flip ) {
             bytebuffer.flip();
-            writeBufferFlipped = true;
+            flipped = true;
         }
 
         int written = 0;
-        NioEndpoint.NioSocketWrapper att = (NioEndpoint.NioSocketWrapper)socket.getAttachment();
+        NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment();
         if ( att == null ) throw new IOException("Key must be cancelled");
         long writeTimeout = att.getWriteTimeout();
         Selector selector = null;
@@ -135,7 +148,7 @@
             //blocking writes must empty the buffer
             //and if remaining==0 then we did empty it
             bytebuffer.clear();
-            writeBufferFlipped = false;
+            flipped = false;
         }
         // If there is data left in the buffer the socket will be registered for
         // write further up the stack. This is to ensure the socket is only
@@ -147,8 +160,27 @@
 
     // ------------------------------------------------------ Protected Methods
 
+    /**
+     * Commit the response.
+     *
+     * @throws IOException an underlying I/O error occurred
+     */
     @Override
-    protected synchronized void addToBB(byte[] buf, int offset, int length)
+    protected void commit() throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            addToBB(headerBuffer, 0, pos);
+        }
+
+    }
+
+
+    private synchronized void addToBB(byte[] buf, int offset, int length)
             throws IOException {
 
         if (length == 0) return;
@@ -159,10 +191,11 @@
         // Keep writing until all the data is written or a non-blocking write
         // leaves data in the buffer
         while (!dataLeft && length > 0) {
-            int thisTime = transfer(buf,offset,length,socketWriteBuffer);
+            int thisTime = transfer(buf,offset,length,socket.getBufHandler().getWriteBuffer());
             length = length - thisTime;
             offset = offset + thisTime;
-            int written = writeToSocket(socketWriteBuffer, isBlocking(), true);
+            int written = writeToSocket(socket.getBufHandler().getWriteBuffer(),
+                    isBlocking(), true);
             if (written == 0) {
                 dataLeft = true;
             } else {
@@ -170,7 +203,7 @@
             }
         }
 
-        NioEndpoint.NioSocketWrapper ka = (NioEndpoint.NioSocketWrapper)socket.getAttachment();
+        NioEndpoint.KeyAttachment ka = (NioEndpoint.KeyAttachment)socket.getAttachment();
         if (ka != null) ka.access();//prevent timeouts for just doing client writes
 
         if (!isBlocking() && length > 0) {
@@ -200,7 +233,7 @@
         //prevent timeout for async,
         SelectionKey key = socket.getIOChannel().keyFor(socket.getPoller().getSelector());
         if (key != null) {
-            NioEndpoint.NioSocketWrapper attach = (NioEndpoint.NioSocketWrapper) key.attachment();
+            NioEndpoint.KeyAttachment attach = (NioEndpoint.KeyAttachment) key.attachment();
             attach.access();
         }
 
@@ -208,7 +241,7 @@
 
         //write to the socket, if there is anything to write
         if (dataLeft) {
-            writeToSocket(socketWriteBuffer, block, !writeBufferFlipped);
+            writeToSocket(socket.getBufHandler().getWriteBuffer(),block, !flipped);
         }
 
         dataLeft = hasMoreDataToFlush();
@@ -219,11 +252,11 @@
                 ByteBufferHolder buffer = bufIter.next();
                 buffer.flip();
                 while (!hasMoreDataToFlush() && buffer.getBuf().remaining()>0) {
-                    transfer(buffer.getBuf(), socketWriteBuffer);
+                    transfer(buffer.getBuf(), socket.getBufHandler().getWriteBuffer());
                     if (buffer.getBuf().remaining() == 0) {
                         bufIter.remove();
                     }
-                    writeToSocket(socketWriteBuffer, block, true);
+                    writeToSocket(socket.getBufHandler().getWriteBuffer(),block, true);
                     //here we must break if we didn't finish the write
                 }
             }
@@ -234,11 +267,64 @@
 
 
     @Override
+    protected boolean hasMoreDataToFlush() {
+        return (flipped && socket.getBufHandler().getWriteBuffer().remaining()>0) ||
+        (!flipped && socket.getBufHandler().getWriteBuffer().position() > 0);
+    }
+
+
+    @Override
     protected void registerWriteInterest() throws IOException {
-        NioEndpoint.NioSocketWrapper att = (NioEndpoint.NioSocketWrapper)socket.getAttachment();
+        NioEndpoint.KeyAttachment att = (NioEndpoint.KeyAttachment)socket.getAttachment();
         if (att == null) {
             throw new IOException("Key must be cancelled");
         }
         att.getPoller().add(socket, SelectionKey.OP_WRITE);
     }
+
+
+    private int transfer(byte[] from, int offset, int length, ByteBuffer to) {
+        int max = Math.min(length, to.remaining());
+        to.put(from, offset, max);
+        return max;
+    }
+
+
+    private void transfer(ByteBuffer from, ByteBuffer to) {
+        int max = Math.min(from.remaining(), to.remaining());
+        ByteBuffer tmp = from.duplicate ();
+        tmp.limit (tmp.position() + max);
+        to.put (tmp);
+        from.position(from.position() + max);
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class SocketOutputBuffer implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res) throws IOException {
+
+            int len = chunk.getLength();
+            int start = chunk.getStart();
+            byte[] b = chunk.getBuffer();
+            addToBB(b, start, len);
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
 }
diff --git a/java/org/apache/coyote/http11/InternalOutputBuffer.java b/java/org/apache/coyote/http11/InternalOutputBuffer.java
new file mode 100644
index 0000000..429326d
--- /dev/null
+++ b/java/org/apache/coyote/http11/InternalOutputBuffer.java
@@ -0,0 +1,239 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.Response;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+/**
+ * Output buffer.
+ *
+ * @author <a href="mailto:remm@apache.org">Remy Maucherat</a>
+ */
+public class InternalOutputBuffer extends AbstractOutputBuffer<Socket>
+    implements ByteChunk.ByteOutputChannel {
+
+    // ----------------------------------------------------------- Constructors
+
+    /**
+     * Default constructor.
+     */
+    public InternalOutputBuffer(Response response, int headerBufferSize) {
+
+        super(response, headerBufferSize);
+
+        outputStreamOutputBuffer = new OutputStreamOutputBuffer();
+
+        socketBuffer = new ByteChunk();
+        socketBuffer.setByteOutputChannel(this);
+    }
+
+    /**
+     * Underlying output stream. Note: protected to assist with unit testing
+     */
+    protected OutputStream outputStream;
+
+
+    /**
+     * Socket buffer.
+     */
+    private final ByteChunk socketBuffer;
+
+
+    /**
+     * Socket buffer (extra buffering to reduce number of packets sent).
+     */
+    private boolean useSocketBuffer = false;
+
+
+    /**
+     * Set the socket buffer size.
+     */
+    @Override
+    public void setSocketBuffer(int socketBufferSize) {
+
+        if (socketBufferSize > 500) {
+            useSocketBuffer = true;
+            socketBuffer.allocate(socketBufferSize, socketBufferSize);
+        } else {
+            useSocketBuffer = false;
+        }
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+    @Override
+    public void init(SocketWrapper<Socket> socketWrapper,
+            AbstractEndpoint<Socket> endpoint) throws IOException {
+
+        outputStream = socketWrapper.getSocket().getOutputStream();
+    }
+
+
+    /**
+     * Recycle the output buffer. This should be called when closing the
+     * connection.
+     */
+    @Override
+    public void recycle() {
+        super.recycle();
+        outputStream = null;
+    }
+
+
+    /**
+     * End processing of current HTTP request.
+     * Note: All bytes of the current request should have been already
+     * consumed. This method only resets all the pointers so that we are ready
+     * to parse the next HTTP request.
+     */
+    @Override
+    public void nextRequest() {
+        super.nextRequest();
+        socketBuffer.recycle();
+    }
+
+
+    // ------------------------------------------------ HTTP/1.1 Output Methods
+
+    /**
+     * Send an acknowledgment.
+     */
+    @Override
+    public void sendAck()
+        throws IOException {
+
+        if (!committed)
+            outputStream.write(Constants.ACK_BYTES);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Commit the response.
+     *
+     * @throws IOException an underlying I/O error occurred
+     */
+    @Override
+    protected void commit()
+        throws IOException {
+
+        // The response is now committed
+        committed = true;
+        response.setCommitted(true);
+
+        if (pos > 0) {
+            // Sending the response header buffer
+            if (useSocketBuffer) {
+                socketBuffer.append(headerBuffer, 0, pos);
+            } else {
+                outputStream.write(headerBuffer, 0, pos);
+            }
+        }
+
+    }
+
+
+    /**
+     * Callback to write data from the buffer.
+     */
+    @Override
+    public void realWriteBytes(byte cbuf[], int off, int len)
+        throws IOException {
+        if (len > 0) {
+            outputStream.write(cbuf, off, len);
+        }
+    }
+
+
+    //-------------------------------------------------- Non-blocking IO methods
+
+    @Override
+    protected boolean hasMoreDataToFlush() {
+        // The blocking connector always blocks until the previous write is
+        // complete so there is never data remaining to flush. This effectively
+        // allows non-blocking code to work with the blocking connector but -
+        // obviously - every write will always block.
+        return false;
+    }
+
+
+    @Override
+    protected void registerWriteInterest() {
+        // NO-OP for non-blocking connector
+    }
+
+
+    @Override
+    protected boolean flushBuffer(boolean block) throws IOException {
+        // Blocking connector so ignore block parameter as this will always use
+        // blocking IO.
+        if (useSocketBuffer) {
+            socketBuffer.flushBuffer();
+        }
+        // Always blocks so never any data left over.
+        return false;
+    }
+
+
+    // ----------------------------------- OutputStreamOutputBuffer Inner Class
+
+    /**
+     * This class is an output buffer which will write data to an output
+     * stream.
+     */
+    protected class OutputStreamOutputBuffer
+        implements OutputBuffer {
+
+
+        /**
+         * Write chunk.
+         */
+        @Override
+        public int doWrite(ByteChunk chunk, Response res)
+            throws IOException {
+
+            int length = chunk.getLength();
+            if (useSocketBuffer) {
+                socketBuffer.append(chunk.getBuffer(), chunk.getStart(),
+                                    length);
+            } else {
+                outputStream.write(chunk.getBuffer(), chunk.getStart(),
+                                   length);
+            }
+            byteCount += chunk.getLength();
+            return chunk.getLength();
+        }
+
+        @Override
+        public long getBytesWritten() {
+            return byteCount;
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/LocalStrings.properties b/java/org/apache/coyote/http11/LocalStrings.properties
index a3c7c63..e55ce1a 100644
--- a/java/org/apache/coyote/http11/LocalStrings.properties
+++ b/java/org/apache/coyote/http11/LocalStrings.properties
@@ -23,6 +23,7 @@
 http11processor.socket.info=Exception getting socket information
 http11processor.socket.ssl=Exception getting SSL attributes
 http11processor.socket.sslreneg=Exception re-negotiating SSL connection
+http11processor.comet.notsupported=The Comet protocol is not supported by this connector
 http11processor.sendfile.error=Error sending data using sendfile. May be caused by invalid request attributes for start/end points
 http11Processor.upgrade=An internal error has occurred as upgraded connections should only be processed by the dedicated upgrade processor implementations
 
diff --git a/java/org/apache/coyote/http11/NpnHandler.java b/java/org/apache/coyote/http11/NpnHandler.java
index 884be85..5c00299 100644
--- a/java/org/apache/coyote/http11/NpnHandler.java
+++ b/java/org/apache/coyote/http11/NpnHandler.java
@@ -21,7 +21,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Interface specific for protocols that negotiate at NPN level, like
@@ -37,7 +37,7 @@
      * @return OPEN if the socket doesn't have the right npn.
      *    CLOSE if processing is done. LONG to request read polling.
      */
-    SocketState process(SocketWrapperBase<S> socket, SocketStatus status);
+    SocketState process(SocketWrapper<S> socket, SocketStatus status);
 
     /**
      * Initialize the npn handler.
diff --git a/java/org/apache/coyote/http11/filters/GzipOutputFilter.java b/java/org/apache/coyote/http11/filters/GzipOutputFilter.java
index c60967b..0e2efe9 100644
--- a/java/org/apache/coyote/http11/filters/GzipOutputFilter.java
+++ b/java/org/apache/coyote/http11/filters/GzipOutputFilter.java
@@ -24,8 +24,6 @@
 import org.apache.coyote.OutputBuffer;
 import org.apache.coyote.Response;
 import org.apache.coyote.http11.OutputFilter;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.ByteChunk;
 
 /**
@@ -36,7 +34,11 @@
 public class GzipOutputFilter implements OutputFilter {
 
 
-    protected static final Log log = LogFactory.getLog(GzipOutputFilter.class);
+    /**
+     * Logger.
+     */
+    protected static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(GzipOutputFilter.class);
 
 
     // ----------------------------------------------------- Instance Variables
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java b/java/org/apache/coyote/http11/upgrade/AbstractProcessor.java
similarity index 76%
rename from java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
rename to java/org/apache/coyote/http11/upgrade/AbstractProcessor.java
index fd82355..46f20ac 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeProcessor.java
+++ b/java/org/apache/coyote/http11/upgrade/AbstractProcessor.java
@@ -28,34 +28,29 @@
 import org.apache.coyote.Processor;
 import org.apache.coyote.Request;
 import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
-public class UpgradeProcessor<S> implements Processor<S>, WebConnection {
+public abstract class AbstractProcessor<S>
+        implements Processor<S>, WebConnection {
 
-    private static final int INFINITE_TIMEOUT = -1;
-
-    private static final Log log = LogFactory.getLog(UpgradeProcessor.class);
-    private static final StringManager sm = StringManager.getManager(UpgradeProcessor.class);
+    protected static final StringManager sm =
+            StringManager.getManager(Constants.Package);
+    protected abstract Log getLog();
 
     private final HttpUpgradeHandler httpUpgradeHandler;
-    private final UpgradeServletInputStream upgradeServletInputStream;
-    private final UpgradeServletOutputStream upgradeServletOutputStream;
+    private final AbstractServletInputStream upgradeServletInputStream;
+    private final AbstractServletOutputStream<S> upgradeServletOutputStream;
 
-
-    public UpgradeProcessor(SocketWrapperBase<?> wrapper, ByteBuffer leftOverInput,
-            HttpUpgradeHandler httpUpgradeHandler, int asyncWriteBufferSize) {
+    protected AbstractProcessor (HttpUpgradeHandler httpUpgradeHandler,
+            AbstractServletInputStream upgradeServletInputStream,
+            AbstractServletOutputStream<S> upgradeServletOutputStream) {
         this.httpUpgradeHandler = httpUpgradeHandler;
-        this.upgradeServletInputStream = new UpgradeServletInputStream(wrapper);
-        this.upgradeServletOutputStream =
-                new UpgradeServletOutputStream(wrapper, asyncWriteBufferSize);
-
-        wrapper.unRead(leftOverInput);
-        wrapper.setTimeout(INFINITE_TIMEOUT);
+        this.upgradeServletInputStream = upgradeServletInputStream;
+        this.upgradeServletOutputStream = upgradeServletOutputStream;
     }
 
 
@@ -88,15 +83,15 @@
         return true;
     }
 
-
     @Override
     public HttpUpgradeHandler getHttpUpgradeHandler() {
         return httpUpgradeHandler;
     }
 
-
     @Override
-    public final SocketState upgradeDispatch(SocketStatus status) throws IOException {
+    public final SocketState upgradeDispatch(SocketStatus status)
+            throws IOException {
+
         if (status == SocketStatus.OPEN_READ) {
             upgradeServletInputStream.onDataAvailable();
         } else if (status == SocketStatus.OPEN_WRITE) {
@@ -105,12 +100,14 @@
             try {
                 upgradeServletInputStream.close();
             } catch (IOException ioe) {
-                log.debug(sm.getString("upgradeProcessor.isCloseFail", ioe));
+                getLog().debug(sm.getString(
+                        "abstractProcessor.isCloseFail", ioe));
             }
             try {
                 upgradeServletOutputStream.close();
             } catch (IOException ioe) {
-                log.debug(sm.getString("upgradeProcessor.osCloseFail", ioe));
+                getLog().debug(sm.getString(
+                        "abstractProcessor.osCloseFail", ioe));
             }
             return SocketState.CLOSED;
         } else {
@@ -124,7 +121,6 @@
         return SocketState.UPGRADED;
     }
 
-
     @Override
     public final void recycle(boolean socketClosing) {
         // Currently a NO-OP as upgrade processors are not recycled.
@@ -138,49 +134,52 @@
         return null;
     }
 
-
     @Override
-    public final SocketState process(SocketWrapperBase<S> socketWrapper) throws IOException {
+    public final SocketState process(SocketWrapper<S> socketWrapper)
+            throws IOException {
         return null;
     }
 
+    @Override
+    public final SocketState event(SocketStatus status) throws IOException {
+        return null;
+    }
 
     @Override
     public final SocketState asyncDispatch(SocketStatus status) {
         return null;
     }
 
-
     @Override
     public void errorDispatch() {
         // NO-OP
     }
 
-
     @Override
     public final SocketState asyncPostProcess() {
         return null;
     }
 
+    @Override
+    public final boolean isComet() {
+        return false;
+    }
 
     @Override
     public final boolean isAsync() {
         return false;
     }
 
-
     @Override
     public final Request getRequest() {
         return null;
     }
 
-
     @Override
     public final void setSslSupport(SSLSupport sslSupport) {
         // NOOP
     }
 
-
     @Override
     public ByteBuffer getLeftoverInput() {
         return null;
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java b/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
similarity index 82%
rename from java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
rename to java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
index cec1f22..9e52896 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeServletInputStream.java
+++ b/java/org/apache/coyote/http11/upgrade/AbstractServletInputStream.java
@@ -21,15 +21,12 @@
 import javax.servlet.ReadListener;
 import javax.servlet.ServletInputStream;
 
-import org.apache.tomcat.util.net.SocketWrapperBase;
 import org.apache.tomcat.util.res.StringManager;
 
-public class UpgradeServletInputStream extends ServletInputStream {
+public abstract class AbstractServletInputStream extends ServletInputStream {
 
     protected static final StringManager sm =
-            StringManager.getManager(UpgradeServletInputStream.class);
-
-    private final SocketWrapperBase<?> socketWrapper;
+            StringManager.getManager(Constants.Package);
 
     private volatile boolean closeRequired = false;
     // Start in blocking-mode
@@ -37,12 +34,6 @@
     private volatile ReadListener listener = null;
     private volatile ClassLoader applicationLoader = null;
 
-
-    public UpgradeServletInputStream(SocketWrapperBase<?> socketWrapper) {
-        this.socketWrapper = socketWrapper;
-    }
-
-
     @Override
     public final boolean isFinished() {
         if (listener == null) {
@@ -68,7 +59,7 @@
         }
 
         try {
-            ready = Boolean.valueOf(socketWrapper.isReady());
+            ready = Boolean.valueOf(doIsReady());
         } catch (IOException e) {
             onError(e);
         }
@@ -126,7 +117,7 @@
         preReadChecks();
 
         try {
-            return socketWrapper.read(listener == null, b, off, len);
+            return doRead(listener == null, b, off, len);
         } catch (IOException ioe) {
             closeRequired = true;
             throw ioe;
@@ -138,7 +129,7 @@
     @Override
     public void close() throws IOException {
         closeRequired = true;
-        socketWrapper.close();
+        doClose();
     }
 
 
@@ -158,7 +149,7 @@
         byte[] b = new byte[1];
         int result;
         try {
-            result = socketWrapper.read(listener == null, b, 0, 1);
+            result = doRead(listener == null, b, 0, 1);
         } catch (IOException ioe) {
             closeRequired = true;
             throw ioe;
@@ -176,6 +167,21 @@
     }
 
 
+    protected final void onAllDataRead() throws IOException {
+        if (listener == null) {
+            return;
+        }
+        Thread thread = Thread.currentThread();
+        ClassLoader originalClassLoader = thread.getContextClassLoader();
+        try {
+            thread.setContextClassLoader(applicationLoader);
+            listener.onAllDataRead();
+        } finally {
+            thread.setContextClassLoader(originalClassLoader);
+        }
+    }
+
+
     protected final void onDataAvailable() throws IOException {
         if (listener == null) {
             return;
@@ -211,4 +217,17 @@
     protected final boolean isCloseRequired() {
         return closeRequired;
     }
+
+
+    protected abstract boolean doIsReady() throws IOException;
+
+    /**
+     * Abstract method to be overridden by concrete implementations. The base
+     * class will ensure that there are no concurrent calls to this method for
+     * the same socket.
+     */
+    protected abstract int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException;
+
+    protected abstract void doClose() throws IOException;
 }
diff --git a/java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
similarity index 89%
rename from java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java
rename to java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
index 793b46a..94742fd 100644
--- a/java/org/apache/coyote/http11/upgrade/UpgradeServletOutputStream.java
+++ b/java/org/apache/coyote/http11/upgrade/AbstractServletOutputStream.java
@@ -23,15 +23,15 @@
 
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.net.DispatchType;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 import org.apache.tomcat.util.res.StringManager;
 
-public class UpgradeServletOutputStream extends ServletOutputStream {
+public abstract class AbstractServletOutputStream<S> extends ServletOutputStream {
 
     protected static final StringManager sm =
-            StringManager.getManager(UpgradeServletOutputStream.class);
+            StringManager.getManager(Constants.Package);
 
-    protected final SocketWrapperBase<?> socketWrapper;
+    protected final SocketWrapper<S> socketWrapper;
 
     // Used to ensure that isReady() and onWritePossible() have a consistent
     // view of buffer and fireListener when determining if the listener should
@@ -62,7 +62,7 @@
     private final int asyncWriteBufferSize;
 
 
-    public UpgradeServletOutputStream(SocketWrapperBase<?> socketWrapper,
+    public AbstractServletOutputStream(SocketWrapper<S> socketWrapper,
             int asyncWriteBufferSize) {
         this.socketWrapper = socketWrapper;
         this.asyncWriteBufferSize = asyncWriteBufferSize;
@@ -134,7 +134,7 @@
     @Override
     public void close() throws IOException {
         closeRequired = true;
-        socketWrapper.close();
+        doClose();
     }
 
 
@@ -151,7 +151,7 @@
     private void writeInternal(byte[] b, int off, int len) throws IOException {
         if (listener == null) {
             // Simple case - blocking IO
-            socketWrapper.write(true, b, off, len);
+            doWrite(true, b, off, len);
         } else {
             // Non-blocking IO
             // If the non-blocking read does not complete, doWrite() will add
@@ -159,7 +159,7 @@
             // write event before this method has finished updating buffer. The
             // writeLock sync makes sure that buffer is updated before the next
             // write executes.
-            int written = socketWrapper.write(false, b, off, len);
+            int written = doWrite(false, b, off, len);
             if (written < len) {
                 if (b == buffer) {
                     // This is a partial write of the existing buffer. Just
@@ -239,4 +239,18 @@
             thread.setContextClassLoader(originalClassLoader);
         }
     }
+
+
+    /**
+     * Abstract method to be overridden by concrete implementations. The base
+     * class will ensure that there are no concurrent calls to this method for
+     * the same socket by ensuring that the writeLock is held when making any
+     * calls to this method.
+     */
+    protected abstract int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException;
+
+    protected abstract void doFlush() throws IOException;
+
+    protected abstract void doClose() throws IOException;
 }
diff --git a/java/org/apache/coyote/http11/upgrade/AprProcessor.java b/java/org/apache/coyote/http11/upgrade/AprProcessor.java
new file mode 100644
index 0000000..2dd619c
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AprProcessor.java
@@ -0,0 +1,46 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.nio.ByteBuffer;
+
+import javax.servlet.http.HttpUpgradeHandler;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class AprProcessor extends AbstractProcessor<Long> {
+
+    private static final Log log = LogFactory.getLog(AprProcessor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = -1;
+
+    public AprProcessor(SocketWrapper<Long> wrapper, ByteBuffer leftoverInput,
+            HttpUpgradeHandler httpUpgradeProcessor, AprEndpoint endpoint,
+            int asyncWriteBufferSize) {
+        super(httpUpgradeProcessor,
+                new AprServletInputStream(wrapper, leftoverInput),
+                new AprServletOutputStream(wrapper, asyncWriteBufferSize, endpoint));
+
+        Socket.timeoutSet(wrapper.getSocket().longValue(), INFINITE_TIMEOUT);
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java b/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
new file mode 100644
index 0000000..add50ef
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AprServletInputStream.java
@@ -0,0 +1,151 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.jni.OS;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class AprServletInputStream extends AbstractServletInputStream {
+
+    private static final Log log = LogFactory.getLog(AprServletInputStream.class);
+
+    private final SocketWrapper<Long> wrapper;
+    private final long socket;
+    private ByteBuffer leftoverInput;
+    private volatile boolean eagain = false;
+    private volatile boolean closed = false;
+
+
+    public AprServletInputStream(SocketWrapper<Long> wrapper, ByteBuffer leftoverInput) {
+        this.wrapper = wrapper;
+        this.socket = wrapper.getSocket().longValue();
+        if (leftoverInput != null) {
+            this.leftoverInput = ByteBuffer.allocate(leftoverInput.remaining());
+            this.leftoverInput.put(leftoverInput);
+        }
+    }
+
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        if (closed) {
+            throw new IOException(sm.getString("apr.closed", Long.valueOf(socket)));
+        }
+
+        if (leftoverInput != null) {
+            if (leftoverInput.remaining() < len) {
+                len = leftoverInput.remaining();
+            }
+            leftoverInput.get(b, off, len);
+            if (leftoverInput.remaining() == 0) {
+                leftoverInput = null;
+            }
+            return len;
+        }
+
+        Lock readLock = wrapper.getBlockingStatusReadLock();
+        WriteLock writeLock = wrapper.getBlockingStatusWriteLock();
+
+        boolean readDone = false;
+        int result = 0;
+        readLock.lock();
+        try {
+            if (wrapper.getBlockingStatus() == block) {
+                result = Socket.recv(socket, b, off, len);
+                readDone = true;
+            }
+        } finally {
+            readLock.unlock();
+        }
+
+        if (!readDone) {
+            writeLock.lock();
+            try {
+                wrapper.setBlockingStatus(block);
+                // Set the current settings for this socket
+                Socket.optSet(socket, Socket.APR_SO_NONBLOCK, (block ? 0 : 1));
+                // Downgrade the lock
+                readLock.lock();
+                try {
+                    writeLock.unlock();
+                    result = Socket.recv(socket, b, off, len);
+                } finally {
+                    readLock.unlock();
+                }
+            } finally {
+                // Should have been released above but may not have been on some
+                // exception paths
+                if (writeLock.isHeldByCurrentThread()) {
+                    writeLock.unlock();
+                }
+            }
+        }
+
+        if (result > 0) {
+            eagain = false;
+            return result;
+        } else if (-result == Status.EAGAIN) {
+            eagain = true;
+            return 0;
+        } else if (-result == Status.APR_EGENERAL && wrapper.isSecure()) {
+            // Not entirely sure why this is necessary. Testing to date has not
+            // identified any issues with this but log it so it can be tracked
+            // if it is suspected of causing issues in the future.
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("apr.read.sslGeneralError",
+                        Long.valueOf(socket), wrapper));
+            }
+            eagain = true;
+            return 0;
+        } else if (-result == Status.APR_EOF) {
+            throw new EOFException(sm.getString("apr.clientAbort"));
+        } else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
+                (-result == Status.APR_OS_START_SYSERR + 10053)) {
+            // 10053 on Windows is connection aborted
+            throw new EOFException(sm.getString("apr.clientAbort"));
+        } else {
+            throw new IOException(sm.getString("apr.read.error",
+                    Integer.valueOf(-result), Long.valueOf(socket), wrapper));
+        }
+    }
+
+
+    @Override
+    protected boolean doIsReady() {
+        return !eagain;
+    }
+
+
+    @Override
+    protected void doClose() throws IOException {
+        closed = true;
+        // AbstractProcessor needs to trigger the close as multiple closes for
+        // APR/native sockets will cause problems.
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
new file mode 100644
index 0000000..e4a66ea
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/AprServletOutputStream.java
@@ -0,0 +1,168 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
+
+import org.apache.tomcat.jni.OS;
+import org.apache.tomcat.jni.Socket;
+import org.apache.tomcat.jni.Status;
+import org.apache.tomcat.util.net.AprEndpoint;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class AprServletOutputStream extends AbstractServletOutputStream<Long> {
+
+    private static final int SSL_OUTPUT_BUFFER_SIZE = 8192;
+
+    private final AprEndpoint endpoint;
+    private final long socket;
+    private volatile boolean closed = false;
+    private final ByteBuffer sslOutputBuffer;
+
+    public AprServletOutputStream(SocketWrapper<Long> socketWrapper,
+            int asyncWriteBufferSize, AprEndpoint endpoint) {
+        super(socketWrapper, asyncWriteBufferSize);
+        this.endpoint = endpoint;
+        this.socket = socketWrapper.getSocket().longValue();
+        if (endpoint.isSSLEnabled()) {
+            sslOutputBuffer = ByteBuffer.allocateDirect(SSL_OUTPUT_BUFFER_SIZE);
+            sslOutputBuffer.position(SSL_OUTPUT_BUFFER_SIZE);
+        } else {
+            sslOutputBuffer = null;
+        }
+    }
+
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        if (closed) {
+            throw new IOException(sm.getString("apr.closed", Long.valueOf(socket)));
+        }
+
+        Lock readLock = socketWrapper.getBlockingStatusReadLock();
+        WriteLock writeLock = socketWrapper.getBlockingStatusWriteLock();
+
+        readLock.lock();
+        try {
+            if (socketWrapper.getBlockingStatus() == block) {
+                return doWriteInternal(b, off, len);
+            }
+        } finally {
+            readLock.unlock();
+        }
+
+        writeLock.lock();
+        try {
+            // Set the current settings for this socket
+            socketWrapper.setBlockingStatus(block);
+            if (block) {
+                Socket.timeoutSet(socket, endpoint.getSoTimeout() * 1000);
+            } else {
+                Socket.timeoutSet(socket, 0);
+            }
+
+            // Downgrade the lock
+            readLock.lock();
+            try {
+                writeLock.unlock();
+                return doWriteInternal(b, off, len);
+            } finally {
+                readLock.unlock();
+            }
+        } finally {
+            // Should have been released above but may not have been on some
+            // exception paths
+            if (writeLock.isHeldByCurrentThread()) {
+                writeLock.unlock();
+            }
+        }
+    }
+
+
+    private int doWriteInternal(byte[] b, int off, int len) throws IOException {
+
+        int start = off;
+        int left = len;
+        int written;
+
+        do {
+            if (endpoint.isSSLEnabled()) {
+                if (sslOutputBuffer.remaining() == 0) {
+                    // Buffer was fully written last time around
+                    sslOutputBuffer.clear();
+                    if (left < SSL_OUTPUT_BUFFER_SIZE) {
+                        sslOutputBuffer.put(b, start, left);
+                    } else {
+                        sslOutputBuffer.put(b, start, SSL_OUTPUT_BUFFER_SIZE);
+                    }
+                    sslOutputBuffer.flip();
+                } else {
+                    // Buffer still has data from previous attempt to write
+                    // APR + SSL requires that exactly the same parameters are
+                    // passed when re-attempting the write
+                }
+                written = Socket.sendb(socket, sslOutputBuffer,
+                        sslOutputBuffer.position(), sslOutputBuffer.limit());
+                if (written > 0) {
+                    sslOutputBuffer.position(
+                            sslOutputBuffer.position() + written);
+                }
+            } else {
+                written = Socket.send(socket, b, start, left);
+            }
+            if (Status.APR_STATUS_IS_EAGAIN(-written)) {
+                written = 0;
+            } else if (-written == Status.APR_EOF) {
+                throw new EOFException(sm.getString("apr.clientAbort"));
+            } else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
+                    (-written == Status.APR_OS_START_SYSERR + 10053)) {
+                // 10053 on Windows is connection aborted
+                throw new EOFException(sm.getString("apr.clientAbort"));
+            } else if (written < 0) {
+                throw new IOException(sm.getString("apr.write.error",
+                        Integer.valueOf(-written), Long.valueOf(socket), socketWrapper));
+            }
+            start += written;
+            left -= written;
+        } while (written > 0 && left > 0);
+
+        if (left > 0) {
+            endpoint.getPoller().add(socket, -1, false, true);
+        }
+        return len - left;
+    }
+
+
+    @Override
+    protected void doFlush() throws IOException {
+        // TODO Auto-generated method stub
+    }
+
+
+    @Override
+    protected void doClose() throws IOException {
+        closed = true;
+        // AbstractProcessor needs to trigger the close as multiple closes for
+        // APR/native sockets will cause problems.
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/BioProcessor.java b/java/org/apache/coyote/http11/upgrade/BioProcessor.java
new file mode 100644
index 0000000..fa11929
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/BioProcessor.java
@@ -0,0 +1,45 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+import javax.servlet.http.HttpUpgradeHandler;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class BioProcessor extends AbstractProcessor<Socket> {
+
+    private static final Log log = LogFactory.getLog(BioProcessor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = 0;
+
+    public BioProcessor(SocketWrapper<Socket> wrapper, ByteBuffer leftoverInput,
+            HttpUpgradeHandler httpUpgradeProcessor,
+            int asyncWriteBufferSize) throws IOException {
+        super(httpUpgradeProcessor, new BioServletInputStream(wrapper, leftoverInput),
+                new BioServletOutputStream(wrapper, asyncWriteBufferSize));
+
+        wrapper.getSocket().setSoTimeout(INFINITE_TIMEOUT);
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/BioServletInputStream.java b/java/org/apache/coyote/http11/upgrade/BioServletInputStream.java
new file mode 100644
index 0000000..0d2ec83
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/BioServletInputStream.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.nio.ByteBuffer;
+
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class BioServletInputStream extends AbstractServletInputStream {
+
+    private final InputStream inputStream;
+    private ByteBuffer leftoverInput;
+
+    public BioServletInputStream(SocketWrapper<Socket> wrapper, ByteBuffer leftoverInput)
+            throws IOException {
+        inputStream = wrapper.getSocket().getInputStream();
+        if (leftoverInput != null) {
+            this.leftoverInput = ByteBuffer.allocate(leftoverInput.remaining());
+            this.leftoverInput.put(leftoverInput);
+        }
+    }
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        if (leftoverInput != null) {
+            if (leftoverInput.remaining() < len) {
+                len = leftoverInput.remaining();
+            }
+            leftoverInput.get(b, off, len);
+            if (leftoverInput.remaining() == 0) {
+                leftoverInput = null;
+            }
+            return len;
+        } else {
+            return inputStream.read(b, off, len);
+        }
+    }
+
+    @Override
+    protected boolean doIsReady() {
+        // Always returns true for BIO
+        return true;
+    }
+
+    @Override
+    protected void doClose() throws IOException {
+        inputStream.close();
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/BioServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/BioServletOutputStream.java
new file mode 100644
index 0000000..233f636
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/BioServletOutputStream.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class BioServletOutputStream extends AbstractServletOutputStream<Socket> {
+
+    private final OutputStream os;
+
+    public BioServletOutputStream(SocketWrapper<Socket> socketWrapper,
+            int asyncWriteBufferSize) throws IOException {
+        super(socketWrapper, asyncWriteBufferSize);
+        os = socketWrapper.getSocket().getOutputStream();
+    }
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        os.write(b, off, len);
+        return len;
+    }
+
+    @Override
+    protected void doFlush() throws IOException {
+        os.flush();
+    }
+
+    @Override
+    protected void doClose() throws IOException {
+        os.close();
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/Constants.java b/java/org/apache/coyote/http11/upgrade/Constants.java
new file mode 100644
index 0000000..d3764a7
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/Constants.java
@@ -0,0 +1,22 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+public class Constants {
+
+    public static final String Package = "org.apache.coyote.http11.upgrade";
+}
diff --git a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties b/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
index e4c29b8..d21c476 100644
--- a/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
+++ b/java/org/apache/coyote/http11/upgrade/LocalStrings.properties
@@ -13,8 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-upgradeProcessor.isCloseFail=Failed to close input stream associated with upgraded connection
-upgradeProcessor.osCloseFail=Failed to close output stream associated with upgraded connection
+abstractProcessor.isCloseFail=Failed to close input stream associated with upgraded connection
+abstractProcessor.osCloseFail=Failed to close output stream associated with upgraded connection
 
 upgrade.sis.isFinished.ise=It is illegal to call isFinished() when the ServletInputStream is not in non-blocking mode (i.e. setReadListener() must be called first)
 upgrade.sis.isReady.ise=It is illegal to call isReady() when the ServletInputStream is not in non-blocking mode (i.e. setReadListener() must be called first)
@@ -26,3 +26,9 @@
 upgrade.sos.writeListener.set=It is illegal to call setWriteListener() more than once for the same upgraded connection
 upgrade.sis.write.ise=It is illegal to call any of the write() methods in non-blocking mode without first checking that there is space available by calling isReady()
 
+apr.clientAbort=The client aborted the connection.
+apr.read.error=Unexpected error [{0}] reading data from the APR/native socket [{1}] with wrapper [{2}].
+apr.read.sslGeneralError=An APR general error was returned by the SSL read operation on APR/native socket [{0}] with wrapper [{1}]. It will be treated as EAGAIN and the socket returned to the poller.
+apr.write.error=Unexpected error [{0}] writing data to the APR/native socket [{1}] with wrapper [{2}].
+apr.closed=The socket [{0}] associated with this connection has been closed.
+
diff --git a/java/org/apache/coyote/http11/upgrade/Nio2Processor.java b/java/org/apache/coyote/http11/upgrade/Nio2Processor.java
new file mode 100644
index 0000000..8fe5af7
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/Nio2Processor.java
@@ -0,0 +1,50 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.nio.ByteBuffer;
+
+import javax.servlet.http.HttpUpgradeHandler;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.Nio2Channel;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class Nio2Processor extends AbstractProcessor<Nio2Channel> {
+
+    private static final Log log = LogFactory.getLog(Nio2Processor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = -1;
+
+    public Nio2Processor(AbstractEndpoint<Nio2Channel> endpoint,
+            SocketWrapper<Nio2Channel> wrapper, ByteBuffer leftoverInput,
+            HttpUpgradeHandler httpUpgradeProcessor,
+            int asyncWriteBufferSize) {
+        super(httpUpgradeProcessor,
+                new Nio2ServletInputStream(wrapper, endpoint),
+                new Nio2ServletOutputStream(wrapper, asyncWriteBufferSize, endpoint));
+
+        wrapper.setTimeout(INFINITE_TIMEOUT);
+        if (leftoverInput != null) {
+            wrapper.getSocket().getBufHandler().getReadBuffer().put(leftoverInput);
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/Nio2ServletInputStream.java b/java/org/apache/coyote/http11/upgrade/Nio2ServletInputStream.java
new file mode 100644
index 0000000..b7e5e2e
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/Nio2ServletInputStream.java
@@ -0,0 +1,226 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.Nio2Channel;
+import org.apache.tomcat.util.net.Nio2Endpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class Nio2ServletInputStream extends AbstractServletInputStream {
+
+    private final AbstractEndpoint<Nio2Channel> endpoint;
+    private final SocketWrapper<Nio2Channel> wrapper;
+    private final Nio2Channel channel;
+    private final CompletionHandler<Integer, SocketWrapper<Nio2Channel>> completionHandler;
+    private boolean flipped = false;
+    private volatile boolean readPending = false;
+    private volatile boolean interest = true;
+
+    public Nio2ServletInputStream(SocketWrapper<Nio2Channel> wrapper, AbstractEndpoint<Nio2Channel> endpoint0) {
+        this.endpoint = endpoint0;
+        this.wrapper = wrapper;
+        this.channel = wrapper.getSocket();
+        this.completionHandler = new CompletionHandler<Integer, SocketWrapper<Nio2Channel>>() {
+            @Override
+            public void completed(Integer nBytes, SocketWrapper<Nio2Channel> attachment) {
+                boolean notify = false;
+                synchronized (completionHandler) {
+                    if (nBytes.intValue() < 0) {
+                        failed(new EOFException(), attachment);
+                    } else {
+                        readPending = false;
+                        if (interest && !Nio2Endpoint.isInline()) {
+                            interest = false;
+                            notify = true;
+                        }
+                    }
+                }
+                if (notify) {
+                    endpoint.processSocket(attachment, SocketStatus.OPEN_READ, false);
+                }
+            }
+            @Override
+            public void failed(Throwable exc, SocketWrapper<Nio2Channel> attachment) {
+                attachment.setError(true);
+                readPending = false;
+                if (exc instanceof AsynchronousCloseException) {
+                    // If already closed, don't call onError and close again
+                    return;
+                }
+                onError(exc);
+                endpoint.processSocket(attachment, SocketStatus.ERROR, true);
+            }
+        };
+    }
+
+    @Override
+    protected boolean doIsReady() throws IOException {
+        synchronized (completionHandler) {
+            if (readPending) {
+                interest = true;
+                return false;
+            }
+            ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+            if (!flipped) {
+                readBuffer.flip();
+                flipped = true;
+            }
+            if (readBuffer.remaining() > 0) {
+                return true;
+            }
+
+            readBuffer.clear();
+            flipped = false;
+            int nRead = fillReadBuffer(false);
+
+            boolean isReady = nRead > 0;
+            if (isReady) {
+                if (!flipped) {
+                    readBuffer.flip();
+                    flipped = true;
+                }
+            } else {
+                interest = true;
+            }
+            return isReady;
+        }
+    }
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        synchronized (completionHandler) {
+            if (readPending) {
+                return 0;
+            }
+
+            ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+
+            if (!flipped) {
+                readBuffer.flip();
+                flipped = true;
+            }
+            int remaining = readBuffer.remaining();
+            // Is there enough data in the read buffer to satisfy this request?
+            if (remaining >= len) {
+                readBuffer.get(b, off, len);
+                return len;
+            }
+
+            // Copy what data there is in the read buffer to the byte array
+            int leftToWrite = len;
+            int newOffset = off;
+            if (remaining > 0) {
+                readBuffer.get(b, off, remaining);
+                leftToWrite -= remaining;
+                newOffset += remaining;
+            }
+
+            // Fill the read buffer as best we can
+            readBuffer.clear();
+            flipped = false;
+            int nRead = fillReadBuffer(block);
+
+            // Full as much of the remaining byte array as possible with the data
+            // that was just read
+            if (nRead > 0) {
+                if (!flipped) {
+                    readBuffer.flip();
+                    flipped = true;
+                }
+                if (nRead > leftToWrite) {
+                    readBuffer.get(b, newOffset, leftToWrite);
+                    leftToWrite = 0;
+                } else {
+                    readBuffer.get(b, newOffset, nRead);
+                    leftToWrite -= nRead;
+                }
+            } else if (nRead == 0) {
+                if (block) {
+                    if (!flipped) {
+                        readBuffer.flip();
+                        flipped = true;
+                    }
+                }
+            } else if (nRead == -1) {
+                throw new EOFException();
+            }
+
+            return len - leftToWrite;
+        }
+    }
+
+    @Override
+    protected void doClose() throws IOException {
+        channel.close();
+    }
+
+    private int fillReadBuffer(boolean block) throws IOException {
+        ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+        int nRead = 0;
+        if (block) {
+            readPending = true;
+            readBuffer.clear();
+            flipped = false;
+            try {
+                nRead = channel.read(readBuffer)
+                        .get(wrapper.getTimeout(), TimeUnit.MILLISECONDS).intValue();
+                readPending = false;
+            } catch (ExecutionException e) {
+                if (e.getCause() instanceof IOException) {
+                    onError(e.getCause());
+                    throw (IOException) e.getCause();
+                } else {
+                    onError(e);
+                    throw new IOException(e);
+                }
+            } catch (InterruptedException e) {
+                onError(e);
+                throw new IOException(e);
+            } catch (TimeoutException e) {
+                SocketTimeoutException ex = new SocketTimeoutException();
+                onError(ex);
+                throw ex;
+            }
+        } else {
+            readPending = true;
+            readBuffer.clear();
+            flipped = false;
+            Nio2Endpoint.startInline();
+            channel.read(readBuffer,
+                    wrapper.getTimeout(), TimeUnit.MILLISECONDS, wrapper, completionHandler);
+            Nio2Endpoint.endInline();
+            if (!readPending) {
+                nRead = readBuffer.position();
+            }
+        }
+        return nRead;
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/Nio2ServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/Nio2ServletOutputStream.java
new file mode 100644
index 0000000..8de562e
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/Nio2ServletOutputStream.java
@@ -0,0 +1,190 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.net.SocketTimeoutException;
+import java.nio.ByteBuffer;
+import java.nio.channels.AsynchronousCloseException;
+import java.nio.channels.CompletionHandler;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Semaphore;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+import org.apache.tomcat.util.net.AbstractEndpoint;
+import org.apache.tomcat.util.net.Nio2Channel;
+import org.apache.tomcat.util.net.Nio2Endpoint;
+import org.apache.tomcat.util.net.SocketStatus;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class Nio2ServletOutputStream extends AbstractServletOutputStream<Nio2Channel> {
+
+    private final AbstractEndpoint<Nio2Channel> endpoint;
+    private final Nio2Channel channel;
+    private final int maxWrite;
+    private final CompletionHandler<Integer, ByteBuffer> completionHandler;
+    private final Semaphore writePending = new Semaphore(1);
+
+    public Nio2ServletOutputStream(SocketWrapper<Nio2Channel> socketWrapper0,
+            int asyncWriteBufferSize, AbstractEndpoint<Nio2Channel> endpoint0) {
+        super(socketWrapper0, asyncWriteBufferSize);
+        this.endpoint = endpoint0;
+        channel = socketWrapper0.getSocket();
+        maxWrite = channel.getBufHandler().getWriteBuffer().capacity();
+        this.completionHandler = new CompletionHandler<Integer, ByteBuffer>() {
+            @Override
+            public void completed(Integer nBytes, ByteBuffer attachment) {
+                if (nBytes.intValue() < 0) {
+                    failed(new EOFException(), attachment);
+                } else if (attachment.hasRemaining()) {
+                    channel.write(attachment, socketWrapper.getTimeout(),
+                            TimeUnit.MILLISECONDS, attachment, completionHandler);
+                } else {
+                    writePending.release();
+                    if (!Nio2Endpoint.isInline()) {
+                        endpoint.processSocket(socketWrapper, SocketStatus.OPEN_WRITE, false);
+                    }
+                }
+            }
+            @Override
+            public void failed(Throwable exc, ByteBuffer attachment) {
+                socketWrapper.setError(true);
+                writePending.release();
+                if (exc instanceof AsynchronousCloseException) {
+                    // If already closed, don't call onError and close again
+                    return;
+                }
+                onError(exc);
+                endpoint.processSocket(socketWrapper, SocketStatus.ERROR, true);
+            }
+        };
+    }
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        int leftToWrite = len;
+        int count = 0;
+        int offset = off;
+
+        while (leftToWrite > 0) {
+            int writeThisLoop;
+            int writtenThisLoop;
+
+            if (leftToWrite > maxWrite) {
+                writeThisLoop = maxWrite;
+            } else {
+                writeThisLoop = leftToWrite;
+            }
+
+            writtenThisLoop = doWriteInternal(block, b, offset, writeThisLoop);
+            if (writtenThisLoop < 0) {
+                throw new EOFException();
+            }
+            count += writtenThisLoop;
+            if (!block && writePending.availablePermits() == 0) {
+                // Prevent concurrent writes in non blocking mode,
+                // leftover data has to be buffered
+                return count;
+            }
+            offset += writtenThisLoop;
+            leftToWrite -= writtenThisLoop;
+
+            if (writtenThisLoop < writeThisLoop) {
+                break;
+            }
+        }
+
+        return count;
+    }
+
+    private int doWriteInternal(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        ByteBuffer buffer = channel.getBufHandler().getWriteBuffer();
+        int written = 0;
+        if (block) {
+            buffer.clear();
+            buffer.put(b, off, len);
+            buffer.flip();
+            try {
+                written = channel.write(buffer).get(socketWrapper.getTimeout(), TimeUnit.MILLISECONDS).intValue();
+            } catch (ExecutionException e) {
+                if (e.getCause() instanceof IOException) {
+                    onError(e.getCause());
+                    throw (IOException) e.getCause();
+                } else {
+                    onError(e);
+                    throw new IOException(e);
+                }
+            } catch (InterruptedException e) {
+                onError(e);
+                throw new IOException(e);
+            } catch (TimeoutException e) {
+                SocketTimeoutException ex = new SocketTimeoutException();
+                onError(ex);
+                throw ex;
+            }
+        } else {
+            if (writePending.tryAcquire()) {
+                buffer.clear();
+                buffer.put(b, off, len);
+                buffer.flip();
+                Nio2Endpoint.startInline();
+                channel.write(buffer, socketWrapper.getTimeout(), TimeUnit.MILLISECONDS, buffer, completionHandler);
+                Nio2Endpoint.endInline();
+                written = len;
+            }
+        }
+        return written;
+    }
+
+    @Override
+    protected void doFlush() throws IOException {
+        try {
+            // Block until a possible non blocking write is done
+            if (writePending.tryAcquire(socketWrapper.getTimeout(), TimeUnit.MILLISECONDS)) {
+                writePending.release();
+                channel.flush().get(socketWrapper.getTimeout(), TimeUnit.MILLISECONDS);
+            } else {
+                throw new TimeoutException();
+            }
+        } catch (ExecutionException e) {
+            if (e.getCause() instanceof IOException) {
+                onError(e.getCause());
+                throw (IOException) e.getCause();
+            } else {
+                onError(e);
+                throw new IOException(e);
+            }
+        } catch (InterruptedException e) {
+            onError(e);
+            throw new IOException(e);
+        } catch (TimeoutException e) {
+            SocketTimeoutException ex = new SocketTimeoutException();
+            onError(ex);
+            throw ex;
+        }
+    }
+
+    @Override
+    protected void doClose() throws IOException {
+        channel.close(true);
+    }
+
+}
diff --git a/java/org/apache/coyote/http11/upgrade/NioProcessor.java b/java/org/apache/coyote/http11/upgrade/NioProcessor.java
new file mode 100644
index 0000000..407029c
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/NioProcessor.java
@@ -0,0 +1,56 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.nio.ByteBuffer;
+
+import javax.servlet.http.HttpUpgradeHandler;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class NioProcessor extends AbstractProcessor<NioChannel> {
+
+    private static final Log log = LogFactory.getLog(NioProcessor.class);
+    @Override
+    protected Log getLog() {return log;}
+
+    private static final int INFINITE_TIMEOUT = -1;
+
+    public NioProcessor(SocketWrapper<NioChannel> wrapper, ByteBuffer leftoverInput,
+            HttpUpgradeHandler httpUpgradeProcessor, NioSelectorPool pool,
+            int asyncWriteBufferSize) {
+        super(httpUpgradeProcessor,
+                new NioServletInputStream(wrapper, pool),
+                new NioServletOutputStream(wrapper, asyncWriteBufferSize, pool));
+
+        wrapper.setTimeout(INFINITE_TIMEOUT);
+        if (leftoverInput != null) {
+            ByteBuffer readBuffer = wrapper.getSocket().getBufHandler().getReadBuffer();
+            if (readBuffer.remaining() > 0) {
+                readBuffer.flip();
+            } else {
+                readBuffer.clear();
+            }
+            readBuffer.put(leftoverInput);
+            readBuffer.flip();
+        }
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/NioServletInputStream.java b/java/org/apache/coyote/http11/upgrade/NioServletInputStream.java
new file mode 100644
index 0000000..5ad99f6
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/NioServletInputStream.java
@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.Selector;
+
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class NioServletInputStream extends AbstractServletInputStream {
+
+    private final NioChannel channel;
+    private final NioSelectorPool pool;
+
+    public NioServletInputStream(SocketWrapper<NioChannel> wrapper,
+            NioSelectorPool pool) {
+        this.channel = wrapper.getSocket();
+        this.pool = pool;
+    }
+
+    @Override
+    protected boolean doIsReady() throws IOException {
+        ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+
+        if (readBuffer.remaining() > 0) {
+            return true;
+        }
+
+        readBuffer.clear();
+        fillReadBuffer(false);
+
+        boolean isReady = readBuffer.position() > 0;
+        readBuffer.flip();
+        return isReady;
+    }
+
+    @Override
+    protected int doRead(boolean block, byte[] b, int off, int len)
+            throws IOException {
+
+        ByteBuffer readBuffer = channel.getBufHandler().getReadBuffer();
+        int remaining = readBuffer.remaining();
+
+        // Is there enough data in the read buffer to satisfy this request?
+        if (remaining >= len) {
+            readBuffer.get(b, off, len);
+            return len;
+        }
+
+        // Copy what data there is in the read buffer to the byte array
+        int leftToWrite = len;
+        int newOffset = off;
+        if (remaining > 0) {
+            readBuffer.get(b, off, remaining);
+            leftToWrite -= remaining;
+            newOffset += remaining;
+        }
+
+        // Fill the read buffer as best we can
+        readBuffer.clear();
+        int nRead = fillReadBuffer(block);
+
+        // Full as much of the remaining byte array as possible with the data
+        // that was just read
+        if (nRead > 0) {
+            readBuffer.flip();
+            if (nRead > leftToWrite) {
+                readBuffer.get(b, newOffset, leftToWrite);
+                leftToWrite = 0;
+            } else {
+                readBuffer.get(b, newOffset, nRead);
+                leftToWrite -= nRead;
+            }
+        } else if (nRead == 0) {
+            readBuffer.flip();
+        } else if (nRead == -1) {
+            // TODO i18n
+            throw new EOFException();
+        }
+
+        return len - leftToWrite;
+    }
+
+
+
+    @Override
+    protected void doClose() throws IOException {
+        channel.close();
+    }
+
+
+    private int fillReadBuffer(boolean block) throws IOException {
+        int nRead;
+        if (block) {
+            Selector selector = null;
+            try {
+                selector = pool.get();
+            } catch ( IOException x ) {
+                // Ignore
+            }
+            try {
+                NioEndpoint.KeyAttachment att =
+                        (NioEndpoint.KeyAttachment) channel.getAttachment();
+                if (att == null) {
+                    throw new IOException("Key must be cancelled.");
+                }
+                nRead = pool.read(channel.getBufHandler().getReadBuffer(),
+                        channel, selector, att.getTimeout());
+            } catch (EOFException eof) {
+                nRead = -1;
+            } finally {
+                if (selector != null) {
+                    pool.put(selector);
+                }
+            }
+        } else {
+            nRead = channel.read(channel.getBufHandler().getReadBuffer());
+        }
+        return nRead;
+    }
+}
diff --git a/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java b/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
new file mode 100644
index 0000000..410ee10
--- /dev/null
+++ b/java/org/apache/coyote/http11/upgrade/NioServletOutputStream.java
@@ -0,0 +1,140 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.coyote.http11.upgrade;
+
+import java.io.IOException;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+
+import org.apache.tomcat.util.net.NioChannel;
+import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.NioSelectorPool;
+import org.apache.tomcat.util.net.SocketWrapper;
+
+public class NioServletOutputStream extends AbstractServletOutputStream<NioChannel> {
+
+    private final NioChannel channel;
+    private final NioSelectorPool pool;
+    private final int maxWrite;
+
+
+    public NioServletOutputStream(SocketWrapper<NioChannel> socketWrapper,
+            int asyncWriteBufferSize, NioSelectorPool pool) {
+        super(socketWrapper, asyncWriteBufferSize);
+        channel = socketWrapper.getSocket();
+        this.pool = pool;
+        maxWrite = channel.getBufHandler().getWriteBuffer().capacity();
+    }
+
+
+    @Override
+    protected int doWrite(boolean block, byte[] b, int off, int len)
+            throws IOException {
+        int leftToWrite = len;
+        int count = 0;
+        int offset = off;
+
+        while (leftToWrite > 0) {
+            int writeThisLoop;
+            int writtenThisLoop;
+
+            if (leftToWrite > maxWrite) {
+                writeThisLoop = maxWrite;
+            } else {
+                writeThisLoop = leftToWrite;
+            }
+
+            writtenThisLoop = doWriteInternal(block, b, offset, writeThisLoop);
+            count += writtenThisLoop;
+            offset += writtenThisLoop;
+            leftToWrite -= writtenThisLoop;
+
+            if (writtenThisLoop < writeThisLoop) {
+                break;
+            }
+        }
+
+        return count;
+    }
+
+    private int doWriteInternal (boolean block, byte[] b, int off, int len)
+            throws IOException {
+        channel.getBufHandler().getWriteBuffer().clear();
+        channel.getBufHandler().getWriteBuffer().put(b, off, len);
+        channel.getBufHandler().getWriteBuffer().flip();
+
+        int written = 0;
+        NioEndpoint.KeyAttachment att =
+                (NioEndpoint.KeyAttachment) channel.getAttachment();
+        if (att == null) {
+            throw new IOException("Key must be cancelled");
+        }
+        long writeTimeout = att.getWriteTimeout();
+        Selector selector = null;
+        try {
+            selector = pool.get();
+        } catch ( IOException x ) {
+            //ignore
+        }
+        try {
+            written = pool.write(channel.getBufHandler().getWriteBuffer(),
+                    channel, selector, writeTimeout, block);
+        } finally {
+            if (selector != null) {
+                pool.put(selector);
+            }
+        }
+        if (written < len) {
+            channel.getPoller().add(channel, SelectionKey.OP_WRITE);
+        }
+        return written;
+    }
+
+
+    @Override
+    protected void doFlush() throws IOException {
+        NioEndpoint.KeyAttachment att =
+                (NioEndpoint.KeyAttachment) channel.getAttachment();
+        if (att == null) {
+            throw new IOException("Key must be cancelled");
+        }
+        long writeTimeout = att.getWriteTimeout();
+        Selector selector = null;
+        try {
+            selector = pool.get();
+        } catch ( IOException x ) {
+            //ignore
+        }
+        try {
+            do {
+                if (channel.flush(true, selector, writeTimeout)) {
+                    break;
+                }
+            } while (true);
+        } finally {
+            if (selector != null) {
+                pool.put(selector);
+            }
+        }
+    }
+
+
+    @Override
+    protected void doClose() throws IOException {
+        channel.close(true);
+    }
+}
diff --git a/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java b/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java
index e1d5f83..8507617 100644
--- a/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java
+++ b/java/org/apache/coyote/spdy/SpdyAprNpnHandler.java
@@ -32,7 +32,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.AprEndpoint;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * Plugin for APR connector providing SPDY support via NPN negotiation.
@@ -90,7 +90,7 @@
     }
 
     @Override
-    public SocketState process(SocketWrapperBase<Long> socketWrapper,
+    public SocketState process(SocketWrapper<Long> socketWrapper,
             SocketStatus status) {
 
         long socket = socketWrapper.getSocket().longValue();
diff --git a/java/org/apache/coyote/spdy/SpdyProcessor.java b/java/org/apache/coyote/spdy/SpdyProcessor.java
index c2fd216..618e41e 100644
--- a/java/org/apache/coyote/spdy/SpdyProcessor.java
+++ b/java/org/apache/coyote/spdy/SpdyProcessor.java
@@ -50,7 +50,7 @@
 import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * A spdy stream ( multiplexed over a spdy tcp connection ) processed by a
@@ -507,12 +507,23 @@
     }
 
     @Override
-    public SocketState process(SocketWrapperBase<S> socket)
+    public boolean isComet() {
+        return false;
+    }
+
+    @Override
+    public SocketState process(SocketWrapper<S> socket)
             throws IOException {
         throw new IOException("Unimplemented");
     }
 
     @Override
+    public SocketState event(SocketStatus status) throws IOException {
+        System.err.println("EVENT: " + status);
+        return null;
+    }
+
+    @Override
     public SocketState asyncDispatch(SocketStatus status) {
         System.err.println("ASYNC DISPATCH: " + status);
         return null;
diff --git a/java/org/apache/coyote/spdy/SpdyProxyProtocol.java b/java/org/apache/coyote/spdy/SpdyProxyProtocol.java
index ab72e3e..07a1442 100644
--- a/java/org/apache/coyote/spdy/SpdyProxyProtocol.java
+++ b/java/org/apache/coyote/spdy/SpdyProxyProtocol.java
@@ -17,9 +17,7 @@
 package org.apache.coyote.spdy;
 
 import java.io.IOException;
-import java.nio.channels.SocketChannel;
-
-import javax.net.ssl.SSLEngine;
+import java.net.Socket;
 
 import org.apache.coyote.AbstractProtocol;
 import org.apache.coyote.ajp.Constants;
@@ -30,11 +28,11 @@
 import org.apache.tomcat.spdy.SpdyContext;
 import org.apache.tomcat.spdy.SpdyContext.SpdyHandler;
 import org.apache.tomcat.spdy.SpdyStream;
-import org.apache.tomcat.util.net.NioChannel;
-import org.apache.tomcat.util.net.NioEndpoint;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler;
+import org.apache.tomcat.util.net.JIoEndpoint;
 import org.apache.tomcat.util.net.SSLImplementation;
 import org.apache.tomcat.util.net.SocketStatus;
-import org.apache.tomcat.util.net.SocketWrapperBase;
+import org.apache.tomcat.util.net.SocketWrapper;
 
 /**
  * SPDY in 'proxy' mode - no SSL and no header compression.
@@ -42,7 +40,7 @@
  * a reverse proxy ( apache, etc ).
  *
  * To configure:
- * &lt;Connector port="8011" protocol="org.apache.coyote.spdy.SpdyProxyNioProtocol"/&gt;
+ * &lt;Connector port="8011" protocol="org.apache.coyote.spdy.SpdyProxyProtocol"/&gt;
  *
  * To test, use
  *   chrome  --use-spdy=no-compress,no-ssl [--enable-websocket-over-spdy]
@@ -50,24 +48,21 @@
  * TODO: Remote information (client ip, certs, etc ) will be sent in X- headers.
  * TODO: if spdy-&gt;spdy proxy, info about original spdy stream for pushes.
  *
- * TODO: This proxy implementation was refactored to use NIO instead of BIO. It
- *       is untested as SPDY/2 is now obsolete and is not supported by current
- *       browsers. This code should be reviewed when work starts on the HTTP/2
- *       implementation.
- *
  */
-public class SpdyProxyProtocol extends AbstractProtocol<NioChannel> {
+public class SpdyProxyProtocol extends AbstractProtocol<Socket> {
     private static final Log log = LogFactory.getLog(SpdyProxyProtocol.class);
 
+    private final JIoEndpoint.Handler cHandler = new TomcatJioHandler();
     private SpdyContext spdyContext;
 
     private boolean compress = false;
 
     public SpdyProxyProtocol() {
-        super(new NioEndpoint());
-        NioEndpoint.Handler cHandler = new TomcatNioHandler();
-        ((NioEndpoint) getEndpoint()).setHandler(cHandler);
+        endpoint = new JIoEndpoint();
+        ((JIoEndpoint) endpoint).setHandler(cHandler);
+        setSoLinger(Constants.DEFAULT_CONNECTION_LINGER);
         setSoTimeout(Constants.DEFAULT_CONNECTION_TIMEOUT);
+        setTcpNoDelay(Constants.DEFAULT_TCP_NO_DELAY);
     }
 
     @Override
@@ -77,7 +72,7 @@
 
     @Override
     protected String getNamePrefix() {
-        return "spdy2-nio";
+        return "spdy2-jio";
     }
 
     @Override
@@ -86,6 +81,11 @@
     }
 
     @Override
+    protected Handler getHandler() {
+        return cHandler;
+    }
+
+    @Override
     public void start() throws Exception {
         super.start();
         spdyContext = new SpdyContext();
@@ -93,13 +93,13 @@
         spdyContext.setHandler(new SpdyHandler() {
             @Override
             public void onStream(SpdyConnection con, SpdyStream ch) throws IOException {
-                SpdyProcessor<NioChannel> sp = new SpdyProcessor<>(con, getEndpoint());
+                SpdyProcessor<Socket> sp = new SpdyProcessor<>(con, endpoint);
                 sp.setAdapter(getAdapter());
                 sp.onSynStream(ch);
             }
         });
         spdyContext.setNetSupport(new NetSupportSocket());
-        spdyContext.setExecutor(getEndpoint().getExecutor());
+        spdyContext.setExecutor(endpoint.getExecutor());
     }
 
     public boolean isCompress() {
@@ -110,7 +110,7 @@
         this.compress = compress;
     }
 
-    public class TomcatNioHandler implements NioEndpoint.Handler {
+    public class TomcatJioHandler implements JIoEndpoint.Handler {
 
         @Override
         public Object getGlobal() {
@@ -122,7 +122,7 @@
         }
 
         @Override
-        public SocketState process(SocketWrapperBase<NioChannel> socket,
+        public SocketState process(SocketWrapper<Socket> socket,
                 SocketStatus status) {
 
             spdyContext.getNetSupport().onAccept(socket.getSocket());
@@ -135,18 +135,8 @@
         }
 
         @Override
-        public void release(SocketWrapperBase<NioChannel> socket) {
-            // TODO Auto-generated method stub
+        public void beforeHandshake(SocketWrapper<Socket> socket) {
         }
 
-        @Override
-        public void release(SocketChannel socket) {
-            // TODO Auto-generated method stub
-        }
-
-        @Override
-        public void onCreateSSLEngine(SSLEngine engine) {
-            // No SSL in proxy. Should be a NO-OP.
-        }
     }
 }
diff --git a/java/org/apache/jasper/EmbeddedServletOptions.java b/java/org/apache/jasper/EmbeddedServletOptions.java
index 138b154..12b04d3 100644
--- a/java/org/apache/jasper/EmbeddedServletOptions.java
+++ b/java/org/apache/jasper/EmbeddedServletOptions.java
@@ -132,12 +132,12 @@
     /**
      * Compiler target VM.
      */
-    private String compilerTargetVM = "1.8";
+    private String compilerTargetVM = "1.7";
 
     /**
      * The compiler source VM.
      */
-    private String compilerSourceVM = "1.8";
+    private String compilerSourceVM = "1.7";
 
     /**
      * The compiler class name.
diff --git a/java/org/apache/jasper/JspC.java b/java/org/apache/jasper/JspC.java
index 2e01cf3..a64f578 100644
--- a/java/org/apache/jasper/JspC.java
+++ b/java/org/apache/jasper/JspC.java
@@ -189,8 +189,8 @@
 
     protected String compiler = null;
 
-    protected String compilerTargetVM = "1.8";
-    protected String compilerSourceVM = "1.8";
+    protected String compilerTargetVM = "1.7";
+    protected String compilerSourceVM = "1.7";
 
     protected boolean classDebugInfo = true;
 
diff --git a/java/org/apache/jasper/Options.java b/java/org/apache/jasper/Options.java
index 7f209c1..2c93e96 100644
--- a/java/org/apache/jasper/Options.java
+++ b/java/org/apache/jasper/Options.java
@@ -126,12 +126,12 @@
     public String getCompiler();
 
     /**
-     * The compiler target VM, e.g. 1.8.
+     * The compiler target VM, e.g. 1.1, 1.2, 1.3, 1.4, 1.5 or 1.6.
      */
     public String getCompilerTargetVM();
 
     /**
-     * The compiler source VM, e.g. 1.8.
+     * The compiler source VM, e.g. 1.3, 1.4, 1.5 or 1.6.
      */
     public String getCompilerSourceVM();
 
diff --git a/java/org/apache/jasper/compiler/JDTCompiler.java b/java/org/apache/jasper/compiler/JDTCompiler.java
index 6e83381..45d4d51 100644
--- a/java/org/apache/jasper/compiler/JDTCompiler.java
+++ b/java/org/apache/jasper/compiler/JDTCompiler.java
@@ -315,12 +315,12 @@
             } else {
                 log.warn("Unknown source VM " + opt + " ignored.");
                 settings.put(CompilerOptions.OPTION_Source,
-                        CompilerOptions.VERSION_1_8);
+                        CompilerOptions.VERSION_1_7);
             }
         } else {
-            // Default to 1.8
+            // Default to 1.7
             settings.put(CompilerOptions.OPTION_Source,
-                    CompilerOptions.VERSION_1_8);
+                    CompilerOptions.VERSION_1_7);
         }
 
         // Target JVM
@@ -361,14 +361,14 @@
             } else {
                 log.warn("Unknown target VM " + opt + " ignored.");
                 settings.put(CompilerOptions.OPTION_TargetPlatform,
-                        CompilerOptions.VERSION_1_8);
+                        CompilerOptions.VERSION_1_7);
             }
         } else {
-            // Default to 1.8
+            // Default to 1.7
             settings.put(CompilerOptions.OPTION_TargetPlatform,
-                    CompilerOptions.VERSION_1_8);
+                    CompilerOptions.VERSION_1_7);
             settings.put(CompilerOptions.OPTION_Compliance,
-                    CompilerOptions.VERSION_1_8);
+                    CompilerOptions.VERSION_1_7);
         }
 
         final IProblemFactory problemFactory =
@@ -464,5 +464,8 @@
         if (! options.isSmapSuppressed()) {
             SmapUtil.installSmap(smap);
         }
+
     }
+
+
 }
diff --git a/java/org/apache/jasper/compiler/PageInfo.java b/java/org/apache/jasper/compiler/PageInfo.java
index 1f337f1..b8948dd 100644
--- a/java/org/apache/jasper/compiler/PageInfo.java
+++ b/java/org/apache/jasper/compiler/PageInfo.java
@@ -57,7 +57,7 @@
     private String session;
     private boolean isSession = true;
     private String bufferValue;
-    private int buffer = 8*1024;
+    private int buffer = 8*1024;    // XXX confirm
     private String autoFlush;
     private boolean isAutoFlush = true;
     private String isThreadSafeValue;
diff --git a/java/org/apache/jasper/compiler/SmapUtil.java b/java/org/apache/jasper/compiler/SmapUtil.java
index b0f0aa8..bcef014 100644
--- a/java/org/apache/jasper/compiler/SmapUtil.java
+++ b/java/org/apache/jasper/compiler/SmapUtil.java
@@ -32,8 +32,6 @@
 
 import org.apache.jasper.JasperException;
 import org.apache.jasper.JspCompilationContext;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * Contains static utilities for generating SMAP data based on the
@@ -182,7 +180,8 @@
     // Installation logic (from Robert Field, JSR-045 spec lead)
     private static class SDEInstaller {
 
-        private final Log log = LogFactory.getLog(SDEInstaller.class);
+        private final org.apache.juli.logging.Log log=
+            org.apache.juli.logging.LogFactory.getLog( SDEInstaller.class );
 
         static final String nameSDE = "SourceDebugExtension";
 
diff --git a/java/org/apache/jasper/resources/LocalStrings.properties b/java/org/apache/jasper/resources/LocalStrings.properties
index bca2629..7dfbd2b 100644
--- a/java/org/apache/jasper/resources/LocalStrings.properties
+++ b/java/org/apache/jasper/resources/LocalStrings.properties
@@ -180,8 +180,8 @@
 \    -xpoweredBy        Add X-Powered-By response header\n\
 \    -trimSpaces        Trim spaces in template text between actions, directives\n\
 \    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
-\    -source <version>   Set the -source argument to the compiler (default 1.8)\n\
-\    -target <version>   Set the -target argument to the compiler (default 1.8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.7)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.7)\n\
 
 jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
 \n\
diff --git a/java/org/apache/jasper/resources/LocalStrings_es.properties b/java/org/apache/jasper/resources/LocalStrings_es.properties
index d6ab33e..0bc82be 100644
--- a/java/org/apache/jasper/resources/LocalStrings_es.properties
+++ b/java/org/apache/jasper/resources/LocalStrings_es.properties
@@ -178,8 +178,8 @@
     \    -xpoweredBy        A\u00F1ade cabecera de respuesta  X-Powered-By\n\
     \    -trimSpaces        Trim spaces in template text between actions, directives\n\
     \    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
-    \    -source <version>   Set the -source argument to the compiler (default 1.8)\n\
-    \    -target <version>   Set the -target argument to the compiler (default 1.8)\n
+    \    -source <version>   Set the -source argument to the compiler (default 1.7)\n\
+    \    -target <version>   Set the -target argument to the compiler (default 1.7)\n
 jspc.webxml.header = <?xml version\="1.0" encoding\="ISO-8859-1"?>\n\
     \n\
     <\!DOCTYPE web-app\n\
diff --git a/java/org/apache/jasper/resources/LocalStrings_fr.properties b/java/org/apache/jasper/resources/LocalStrings_fr.properties
index da0dd38..45b8aaf 100644
--- a/java/org/apache/jasper/resources/LocalStrings_fr.properties
+++ b/java/org/apache/jasper/resources/LocalStrings_fr.properties
@@ -113,8 +113,8 @@
 \    -sax2 <driverclassname>  Le nom de classe du Driver SAX 2.0 \u00e0 utiliser\n\
 \    -trimSpaces        Trim spaces in template text between actions, directives\n\
 \    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
-\    -source <version>   Set the -source argument to the compiler (default 1.8)\n\
-\    -target <version>   Set the -target argument to the compiler (default 1.8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.7)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.7)\n\
 
 jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
 \n\
diff --git a/java/org/apache/jasper/resources/LocalStrings_ja.properties b/java/org/apache/jasper/resources/LocalStrings_ja.properties
index e0ff499..33812f3 100644
--- a/java/org/apache/jasper/resources/LocalStrings_ja.properties
+++ b/java/org/apache/jasper/resources/LocalStrings_ja.properties
@@ -155,8 +155,8 @@
 \    -trimSpaces        \u30a2\u30af\u30b7\u30e7\u30f3\u3084\u6307\u793a\u5b50\u306e\u9593\u306e\u30c6\u30f3\u30d7\u30ec\u30fc\u30c8\u30c6\u30ad\u30b9\u30c8\u4e2d\u306e\u30b9\u30da\u30fc\u30b9\u3092\u524a\u9664\n\
 \    -trimSpaces        Trim spaces in template text between actions, directives\n\
 \    -javaEncoding <enc> Set the encoding charset for Java classes (default UTF-8)\n\
-\    -source <version>   Set the -source argument to the compiler (default 1.8)\n\
-\    -target <version>   Set the -target argument to the compiler (default 1.8)\n\
+\    -source <version>   Set the -source argument to the compiler (default 1.7)\n\
+\    -target <version>   Set the -target argument to the compiler (default 1.7)\n\
 
 jspc.webxml.header=<?xml version="1.0" encoding="ISO-8859-1"?>\n\
 \n\
diff --git a/java/org/apache/jasper/security/SecurityClassLoad.java b/java/org/apache/jasper/security/SecurityClassLoad.java
index d4f95cc..109a700 100644
--- a/java/org/apache/jasper/security/SecurityClassLoad.java
+++ b/java/org/apache/jasper/security/SecurityClassLoad.java
@@ -18,9 +18,6 @@
 
 package org.apache.jasper.security;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * Static class used to preload java classes when using the
  * Java SecurityManager so that the defineClassInPackage
@@ -29,7 +26,8 @@
 
 public final class SecurityClassLoad {
 
-    private static final Log log = LogFactory.getLog(SecurityClassLoad.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( SecurityClassLoad.class );
 
     public static void securityClassLoad(ClassLoader loader){
 
diff --git a/java/org/apache/jasper/servlet/JspServletWrapper.java b/java/org/apache/jasper/servlet/JspServletWrapper.java
index 551d320..2e4176e 100644
--- a/java/org/apache/jasper/servlet/JspServletWrapper.java
+++ b/java/org/apache/jasper/servlet/JspServletWrapper.java
@@ -286,7 +286,7 @@
             } else {
                 target = getServlet();
             }
-            if (target instanceof JspSourceDependent) {
+            if (target != null && target instanceof JspSourceDependent) {
                 return ((JspSourceDependent) target).getDependants();
             }
         } catch (AbstractMethodError ame) {
diff --git a/java/org/apache/jasper/xmlparser/UCSReader.java b/java/org/apache/jasper/xmlparser/UCSReader.java
index a97ce09..3e86f35 100644
--- a/java/org/apache/jasper/xmlparser/UCSReader.java
+++ b/java/org/apache/jasper/xmlparser/UCSReader.java
@@ -21,9 +21,6 @@
 import java.io.InputStream;
 import java.io.Reader;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * Reader for UCS-2 and UCS-4 encodings.
  * (i.e., encodings from ISO-10646-UCS-(2|4)).
@@ -32,7 +29,8 @@
  */
 public class UCSReader extends Reader {
 
-    private final Log log = LogFactory.getLog(UCSReader.class);
+    private final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UCSReader.class );
 
     //
     // Constants
diff --git a/java/org/apache/jasper/xmlparser/UTF8Reader.java b/java/org/apache/jasper/xmlparser/UTF8Reader.java
index ac93d74..f286680 100644
--- a/java/org/apache/jasper/xmlparser/UTF8Reader.java
+++ b/java/org/apache/jasper/xmlparser/UTF8Reader.java
@@ -23,8 +23,6 @@
 import java.io.UTFDataFormatException;
 
 import org.apache.jasper.compiler.Localizer;
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 
 /**
  * @author Andy Clark, IBM
@@ -32,7 +30,8 @@
 public class UTF8Reader
     extends Reader {
 
-    private final Log log = LogFactory.getLog(UTF8Reader.class);
+    private final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( UTF8Reader.class );
 
     // debugging
 
diff --git a/java/org/apache/juli/AsyncFileHandler.java b/java/org/apache/juli/AsyncFileHandler.java
index 7401cd2..46ef98e 100644
--- a/java/org/apache/juli/AsyncFileHandler.java
+++ b/java/org/apache/juli/AsyncFileHandler.java
@@ -40,27 +40,20 @@
  */
 public class AsyncFileHandler extends FileHandler {
 
-    public static final int OVERFLOW_DROP_LAST    = 1;
-    public static final int OVERFLOW_DROP_FIRST   = 2;
-    public static final int OVERFLOW_DROP_FLUSH   = 3;
+    public static final int OVERFLOW_DROP_LAST = 1;
+    public static final int OVERFLOW_DROP_FIRST = 2;
+    public static final int OVERFLOW_DROP_FLUSH = 3;
     public static final int OVERFLOW_DROP_CURRENT = 4;
 
-    public static final int DEFAULT_OVERFLOW_DROP_TYPE = 1;
-    public static final int DEFAULT_MAX_RECORDS        = 10000;
-    public static final int DEFAULT_LOGGER_SLEEP_TIME  = 1000;
-
     public static final int OVERFLOW_DROP_TYPE = Integer.parseInt(
-            System.getProperty("org.apache.juli.AsyncOverflowDropType",
-                               Integer.toString(DEFAULT_OVERFLOW_DROP_TYPE)));
-    public static final int MAX_RECORDS = Integer.parseInt(
-            System.getProperty("org.apache.juli.AsyncMaxRecordCount",
-                               Integer.toString(DEFAULT_MAX_RECORDS)));
+            System.getProperty("org.apache.juli.AsyncOverflowDropType","1"));
+    public static final int DEFAULT_MAX_RECORDS = Integer.parseInt(
+            System.getProperty("org.apache.juli.AsyncMaxRecordCount","10000"));
     public static final int LOGGER_SLEEP_TIME = Integer.parseInt(
-            System.getProperty("org.apache.juli.AsyncLoggerPollInterval",
-                               Integer.toString(DEFAULT_LOGGER_SLEEP_TIME)));
+            System.getProperty("org.apache.juli.AsyncLoggerPollInterval","1000"));
 
     protected static final LinkedBlockingDeque<LogEntry> queue =
-            new LinkedBlockingDeque<>(MAX_RECORDS);
+            new LinkedBlockingDeque<>(DEFAULT_MAX_RECORDS);
 
     protected static final LoggerThread logger = new LoggerThread();
 
diff --git a/java/org/apache/juli/DateFormatCache.java b/java/org/apache/juli/DateFormatCache.java
index 0297f30..ecbf31e 100644
--- a/java/org/apache/juli/DateFormatCache.java
+++ b/java/org/apache/juli/DateFormatCache.java
@@ -118,6 +118,9 @@
 
         private Cache(Cache parent) {
             cache = new String[cacheSize];
+            for (int i = 0; i < cacheSize; i++) {
+                cache[i] = null;
+            }
             formatter = new SimpleDateFormat(format, Locale.US);
             formatter.setTimeZone(TimeZone.getDefault());
             this.parent = parent;
diff --git a/java/org/apache/juli/logging/DirectJDKLog.java b/java/org/apache/juli/logging/DirectJDKLog.java
index b5c565a..53b271a 100644
--- a/java/org/apache/juli/logging/DirectJDKLog.java
+++ b/java/org/apache/juli/logging/DirectJDKLog.java
@@ -50,10 +50,11 @@
                 // it is also possible that the user modified jre/lib/logging.properties -
                 // but that's really stupid in most cases
                 Logger root=Logger.getLogger("");
-                for (Handler handler : root.getHandlers()) {
+                Handler handlers[]=root.getHandlers();
+                for( int i=0; i< handlers.length; i++ ) {
                     // I only care about console - that's what's used in default config anyway
-                    if (handler instanceof  ConsoleHandler) {
-                        handler.setFormatter(fmt);
+                    if( handlers[i] instanceof  ConsoleHandler ) {
+                        handlers[i].setFormatter(fmt);
                     }
                 }
             } catch( Throwable t ) {
diff --git a/java/org/apache/naming/NamingContext.java b/java/org/apache/naming/NamingContext.java
index 98ae5e2..c78f2ce 100644
--- a/java/org/apache/naming/NamingContext.java
+++ b/java/org/apache/naming/NamingContext.java
@@ -40,9 +40,6 @@
 import javax.naming.Referenceable;
 import javax.naming.spi.NamingManager;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * Catalina JNDI Context implementation.
  *
@@ -60,7 +57,8 @@
     protected static final NameParser nameParser = new NameParserImpl();
 
 
-    private static final Log log = LogFactory.getLog(NamingContext.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(NamingContext.class);
 
 
     // ----------------------------------------------------------- Constructors
diff --git a/java/org/apache/naming/SelectorContext.java b/java/org/apache/naming/SelectorContext.java
index 06a6474..3fcbdd5 100644
--- a/java/org/apache/naming/SelectorContext.java
+++ b/java/org/apache/naming/SelectorContext.java
@@ -28,9 +28,6 @@
 import javax.naming.NamingEnumeration;
 import javax.naming.NamingException;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * Catalina JNDI Context implementation.
  *
@@ -60,7 +57,8 @@
     public static final String IC_PREFIX = "IC_";
 
 
-    private static final Log log = LogFactory.getLog(SelectorContext.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(SelectorContext.class);
 
     // ----------------------------------------------------------- Constructors
 
diff --git a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
index 5c9deeb..aaa3303 100644
--- a/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
+++ b/java/org/apache/tomcat/dbcp/dbcp2/PoolingConnection.java
@@ -321,23 +321,23 @@
                 @SuppressWarnings({"rawtypes", "unchecked"}) // Unable to find way to avoid this
                 PoolablePreparedStatement pps = new PoolablePreparedStatement(
                         getDelegate().prepareStatement(key.getSql()), key, _pstmtPool, this);
-                return new DefaultPooledObject<>(pps);
+                return new DefaultPooledObject<DelegatingPreparedStatement>(pps);
             }
-            return new DefaultPooledObject<>(
+            return new DefaultPooledObject<DelegatingPreparedStatement>(
                     new PoolableCallableStatement(getDelegate().prepareCall( key.getSql()), key, _pstmtPool, this));
         } else if (null == key.getResultSetType() && null == key.getResultSetConcurrency()){
             @SuppressWarnings({"rawtypes", "unchecked"}) // Unable to find way to avoid this
             PoolablePreparedStatement pps = new PoolablePreparedStatement(
                     getDelegate().prepareStatement(key.getSql(), key.getAutoGeneratedKeys().intValue()), key, _pstmtPool, this);
-            return new DefaultPooledObject<>(pps);
+            return new DefaultPooledObject<DelegatingPreparedStatement>(pps);
         } else { // Both _resultSetType and _resultSetConcurrency are non-null here (both or neither are set by constructors)
             if(key.getStmtType() == StatementType.PREPARED_STATEMENT) {
                 @SuppressWarnings({"rawtypes", "unchecked"}) // Unable to find way to avoid this
                 PoolablePreparedStatement pps = new PoolablePreparedStatement(getDelegate().prepareStatement(
                         key.getSql(), key.getResultSetType().intValue(),key.getResultSetConcurrency().intValue()), key, _pstmtPool, this);
-                return new DefaultPooledObject<>(pps);
+                return new DefaultPooledObject<DelegatingPreparedStatement>(pps);
             }
-            return new DefaultPooledObject<>(
+            return new DefaultPooledObject<DelegatingPreparedStatement>(
                     new PoolableCallableStatement( getDelegate().prepareCall(
                             key.getSql(),key.getResultSetType().intValue(), key.getResultSetConcurrency().intValue()), key, _pstmtPool, this));
         }
diff --git a/java/org/apache/tomcat/util/Diagnostics.java b/java/org/apache/tomcat/util/Diagnostics.java
index 74b3479..8d61e22 100644
--- a/java/org/apache/tomcat/util/Diagnostics.java
+++ b/java/org/apache/tomcat/util/Diagnostics.java
@@ -60,8 +60,6 @@
 import java.util.logging.LogManager;
 import java.util.logging.LoggingMXBean;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 public class Diagnostics {
@@ -75,7 +73,8 @@
     private static final String CRLF = "\r\n";
     private static final String vminfoSystemProperty = "java.vm.info";
 
-    private static final Log log = LogFactory.getLog(Diagnostics.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(Diagnostics.class);
 
     private static final SimpleDateFormat timeformat =
         new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
diff --git a/java/org/apache/tomcat/util/IntrospectionUtils.java b/java/org/apache/tomcat/util/IntrospectionUtils.java
index 073cf8b..124eaf8 100644
--- a/java/org/apache/tomcat/util/IntrospectionUtils.java
+++ b/java/org/apache/tomcat/util/IntrospectionUtils.java
@@ -23,16 +23,14 @@
 import java.net.UnknownHostException;
 import java.util.Hashtable;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * Utils for introspection and reflection
  */
 public final class IntrospectionUtils {
 
 
-    private static final Log log = LogFactory.getLog(IntrospectionUtils.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( IntrospectionUtils.class );
 
     /**
      * Find a method with the right name If found, call the method ( if param is
diff --git a/java/org/apache/tomcat/util/bcel/classfile/FastDataInputStream.java b/java/org/apache/tomcat/util/bcel/classfile/FastDataInputStream.java
new file mode 100644
index 0000000..cce1a03
--- /dev/null
+++ b/java/org/apache/tomcat/util/bcel/classfile/FastDataInputStream.java
@@ -0,0 +1,234 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bcel.classfile;
+
+import java.io.BufferedInputStream;
+import java.io.DataInput;
+import java.io.DataInputStream;
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * A "FastDataInputStream" that get the numbers from buffer and from the target
+ * directly instead of "DataInputStream".
+ */
+class FastDataInputStream extends BufferedInputStream implements DataInput {
+
+    private final byte readBuffer[] = new byte[8];
+
+
+    public FastDataInputStream(InputStream in, int size) {
+        super(in, size);
+    }
+
+
+    @Override
+    public final int read(byte b[]) throws IOException {
+        return this.read(b, 0, b.length);
+    }
+
+
+    @Override
+    public final void readFully(byte b[]) throws IOException {
+        readFully(b, 0, b.length);
+    }
+
+
+    @Override
+    public final void readFully(byte b[], int off, int len) throws IOException {
+        if (len < 0)
+            throw new IndexOutOfBoundsException();
+        // Total read
+        int sum = 0;
+        // Current read
+        int cur = 0;
+        for(; sum < len; sum += cur){
+            cur = read(b, off + sum, len - sum);
+            if(cur < 0)
+                throw new EOFException();
+            sum += cur;
+        }
+    }
+
+
+    @Override
+    public boolean readBoolean() throws IOException {
+        if (pos >= count) {
+            fillNew();
+            if (pos >= count)
+                throw new EOFException();
+        }
+        int ch = this.buf[pos++] & 0xff;
+        return (ch != 0);
+    }
+
+
+    @Override
+    public final byte readByte() throws IOException {
+        if (pos >= count) {
+            fillNew();
+            if (pos >= count)
+                throw new EOFException();
+        }
+        return this.buf[pos++];
+    }
+
+
+    @Override
+    public int readUnsignedByte() throws IOException {
+        if (pos >= count) {
+            fillNew();
+            if (pos >= count)
+                throw new EOFException();
+        }
+        int ch = this.buf[pos++] & 0xff;
+        return ch;
+    }
+
+
+    @Override
+    public final short readShort() throws IOException {
+        if(pos + 1 >= count){
+            fillNew();
+            if(pos + 1 >= count) throw new EOFException();
+        }
+        int ch1 = this.buf[pos++] & 0xff;
+        int ch2 = this.buf[pos++] & 0xff;
+        return (short)((ch1 << 8) + ch2);
+    }
+
+
+    @Override
+    public int readUnsignedShort() throws IOException{
+        if(pos + 1 >= count) {
+            fillNew();
+            if(pos + 1 >= count) throw new EOFException();
+        }
+
+        int ch1 = this.buf[pos++] & 0xff;
+        int ch2 = this.buf[pos++] & 0xff;
+        return (ch1 << 8) + ch2;
+    }
+
+
+    @Override
+    public final char readChar() throws IOException {
+        if(pos + 1 >= count) {
+            fillNew();
+            if(pos + 1 >= count) throw new EOFException();
+        }
+        int ch1 = this.buf[pos++] & 0xff;
+        int ch2 = this.buf[pos++] & 0xff;
+        return (char)((ch1 << 8) + ch2);
+    }
+
+
+    @Override
+    public final int readInt() throws IOException {
+        if(pos + 3 >= count){
+            fillNew();
+            if(pos + 3 >= count) throw new EOFException();
+        }
+        int ch1 = this.buf[pos++] & 0xff;
+        int ch2 = this.buf[pos++] & 0xff;
+        int ch3 = this.buf[pos++] & 0xff;
+        int ch4 = this.buf[pos++] & 0xff;
+        return ((ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4);
+    }
+
+
+    @Override
+    public final long readLong() throws IOException {
+        readFully(readBuffer, 0, 8);
+        return (((long)readBuffer[0] << 56) +
+                ((long)(readBuffer[1] & 255) << 48) +
+                ((long)(readBuffer[2] & 255) << 40) +
+                ((long)(readBuffer[3] & 255) << 32) +
+                ((long)(readBuffer[4] & 255) << 24) +
+                ((readBuffer[5] & 255) << 16) +
+                ((readBuffer[6] & 255) <<  8) +
+                (readBuffer[7] & 255));
+    }
+
+
+    @Override
+    public final float readFloat() throws IOException {
+        return Float.intBitsToFloat(readInt());
+    }
+
+
+    @Override
+    public double readDouble() throws IOException {
+        return Double.longBitsToDouble(readLong());
+    }
+
+
+    @Override
+    public final String readUTF() throws IOException {
+        return DataInputStream.readUTF(this);
+    }
+
+
+    private void fillNew() throws IOException {
+        int remain = 0;
+        if(pos < count){
+            remain = count - pos;
+            System.arraycopy(buf, pos, buf, 0, remain);
+        }
+        pos = 0;
+        int n = this.in.read(buf, remain, buf.length - remain);
+        count = pos + n + remain;
+    }
+
+
+    @Override
+    public int skipBytes(int n) throws IOException {
+        int avail = count - pos;
+        // Total Skipped
+        int sum = 0;
+        // Current skipped
+        int cur = 0;
+        if (avail <= 0) {
+            // buffer is exhausted, read via stream directly
+            while (sum < n && (cur = (int) in.skip(n - sum)) > 0) {
+                sum += cur;
+            }
+            return sum;
+        }
+        // Data in the buffer is not enough
+        if(n > avail){
+            // Skip the data in buffer
+            pos += avail;
+            sum += avail;
+            // Read via stream
+            while (sum < n && (cur = (int) in.skip(n - sum)) > 0) {
+                sum += cur;
+            }
+            return sum;
+        }
+        pos += n;
+        return n;
+    }
+
+
+    @Override
+    public String readLine() throws IOException {
+        // Unimplemented
+        throw new UnsupportedOperationException();
+    }
+}
\ No newline at end of file
diff --git a/java/org/apache/tomcat/util/buf/StringCache.java b/java/org/apache/tomcat/util/buf/StringCache.java
index d0d1ad8..da65fee 100644
--- a/java/org/apache/tomcat/util/buf/StringCache.java
+++ b/java/org/apache/tomcat/util/buf/StringCache.java
@@ -22,9 +22,6 @@
 import java.util.Map.Entry;
 import java.util.TreeMap;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * This class implements a String cache for ByteChunk and CharChunk.
  *
@@ -33,7 +30,8 @@
 public class StringCache {
 
 
-    private static final Log log = LogFactory.getLog(StringCache.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( StringCache.class );
 
 
     // ------------------------------------------------------- Static Variables
diff --git a/java/org/apache/tomcat/util/descriptor/tld/TldResourcePath.java b/java/org/apache/tomcat/util/descriptor/tld/TldResourcePath.java
index 2007c03..5100f35 100644
--- a/java/org/apache/tomcat/util/descriptor/tld/TldResourcePath.java
+++ b/java/org/apache/tomcat/util/descriptor/tld/TldResourcePath.java
@@ -127,6 +127,14 @@
         }
     }
 
+    /**
+     * @deprecated Renamed, as it is not just a getter method. Use {@link #openJar()}.
+     */
+    @Deprecated
+    public Jar getJar() throws IOException {
+        return openJar();
+    }
+
     public Jar openJar() throws IOException {
         if (entryName == null) {
             return null;
diff --git a/java/org/apache/tomcat/util/descriptor/web/WebXml.java b/java/org/apache/tomcat/util/descriptor/web/WebXml.java
index 6f54e84..55bada9 100644
--- a/java/org/apache/tomcat/util/descriptor/web/WebXml.java
+++ b/java/org/apache/tomcat/util/descriptor/web/WebXml.java
@@ -37,8 +37,6 @@
 import javax.servlet.descriptor.JspPropertyGroupDescriptor;
 import javax.servlet.descriptor.TaglibDescriptor;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.descriptor.XmlIdentifiers;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -58,7 +56,8 @@
     private static final StringManager sm =
         StringManager.getManager(Constants.PACKAGE_NAME);
 
-    private static final Log log = LogFactory.getLog(WebXml.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog(WebXml.class);
 
     // Global defaults are overridable but Servlets and Servlet mappings need to
     // be unique. Duplicates normally trigger an error. This flag indicates if
diff --git a/java/org/apache/tomcat/util/http/HttpMessages.java b/java/org/apache/tomcat/util/http/HttpMessages.java
index 334c0bb..25d1347 100644
--- a/java/org/apache/tomcat/util/http/HttpMessages.java
+++ b/java/org/apache/tomcat/util/http/HttpMessages.java
@@ -110,6 +110,43 @@
 
 
     /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param message The message string to be filtered
+     */
+    public static String filter(String message) {
+
+        if (message == null) {
+            return (null);
+        }
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuilder result = new StringBuilder(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+    }
+
+    /**
      * Is the provided message safe to use in an HTTP header. Safe messages must
      * meet the requirements of RFC2616 - i.e. must consist only of TEXT.
      *
diff --git a/java/org/apache/tomcat/util/http/Parameters.java b/java/org/apache/tomcat/util/http/Parameters.java
index 18fa655..ddf662d 100644
--- a/java/org/apache/tomcat/util/http/Parameters.java
+++ b/java/org/apache/tomcat/util/http/Parameters.java
@@ -26,8 +26,6 @@
 import java.util.LinkedHashMap;
 import java.util.Map;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.buf.B2CConverter;
 import org.apache.tomcat.util.buf.ByteChunk;
 import org.apache.tomcat.util.buf.MessageBytes;
@@ -41,7 +39,8 @@
  */
 public final class Parameters {
 
-    private static final Log log = LogFactory.getLog(Parameters.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(Parameters.class );
 
     private static final UserDataHelper userDataLog = new UserDataHelper(log);
 
diff --git a/java/org/apache/tomcat/util/net/AbstractEndpoint.java b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
index 59cfe8f..31ce4f0 100644
--- a/java/org/apache/tomcat/util/net/AbstractEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AbstractEndpoint.java
@@ -52,11 +52,9 @@
 
     protected static final String DEFAULT_CIPHERS = "HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5";
 
-    protected static final StringManager sm = StringManager.getManager(
-            AbstractEndpoint.class.getPackage().getName());
+    protected static final StringManager sm = StringManager.getManager("org.apache.tomcat.util.net.res");
 
-    public static interface Handler<S> {
-
+    public static interface Handler {
         /**
          * Different types of socket states to react upon.
          */
@@ -68,18 +66,6 @@
 
 
         /**
-         * Process the provided socket with the given current status.
-         *
-         * @param socket The socket to process
-         * @param status The current socket status
-         *
-         * @return The state of the socket after processing
-         */
-        public SocketState process(SocketWrapperBase<S> socket,
-                SocketStatus status);
-
-
-        /**
          * Obtain the GlobalRequestProcessor associated with the handler.
          */
         public Object getGlobal();
@@ -141,13 +127,10 @@
                     // Ignore
                 }
                 long now = System.currentTimeMillis();
-                for (SocketWrapperBase<S> socket : waitingRequests) {
-                    long asyncTimeout = socket.getAsyncTimeout();
-                    if (asyncTimeout > 0) {
-                        long asyncStart = socket.getLastAsyncStart();
-                        if ((now - asyncStart) > asyncTimeout) {
-                            processSocket(socket, SocketStatus.TIMEOUT, true);
-                        }
+                for (SocketWrapper<S> socket : waitingRequests) {
+                    long access = socket.getLastAccess();
+                    if (socket.getTimeout() > 0 && (now - access) > socket.getTimeout()) {
+                        processSocket(socket, SocketStatus.TIMEOUT, true);
                     }
                 }
 
@@ -212,20 +195,6 @@
     // ----------------------------------------------------------------- Properties
 
     /**
-     * Has the user requested that send file be used where possible?
-     */
-    private boolean useSendfile = true;
-    public boolean getUseSendfile() {
-        return useSendfile;
-    }
-    public void setUseSendfile(boolean useSendfile) {
-        this.useSendfile = useSendfile;
-    }
-
-
-
-
-    /**
      * Time to wait for the internal executor (if used) to terminate when the
      * endpoint is stopped in milliseconds. Defaults to 5000 (5 seconds).
      */
@@ -709,11 +678,11 @@
      * @param dispatch      Should the processing be performed on a new
      *                          container thread
      */
-    public abstract void processSocket(SocketWrapperBase<S> socketWrapper,
+    public abstract void processSocket(SocketWrapper<S> socketWrapper,
             SocketStatus socketStatus, boolean dispatch);
 
 
-    public void executeNonBlockingDispatches(SocketWrapperBase<S> socketWrapper) {
+    public void executeNonBlockingDispatches(SocketWrapper<S> socketWrapper) {
         /*
          * This method is called when non-blocking IO is initiated by defining
          * a read and/or write listener in a non-container thread. It is called
@@ -721,7 +690,7 @@
          * onWritePossible() and/or onDataAvailable() as appropriate are made by
          * the container.
          *
-         * Processing the dispatches requires (for APR/native at least)
+         * Processing the dispatches requires (for BIO and APR/native at least)
          * that the socket has been added to the waitingRequests queue. This may
          * not have occurred by the time that the non-container thread completes
          * triggering the call to this method. Therefore, the coded syncs on the
@@ -848,6 +817,13 @@
     }
 
     protected abstract Log getLog();
+    // Flags to indicate optional feature support
+    // Some of these are always hard-coded, some are hard-coded to false (i.e.
+    // the endpoint does not support them) and some are configurable.
+    public abstract boolean getUseSendfile();
+    public abstract boolean getUseComet();
+    public abstract boolean getUseCometTimeout();
+    public abstract boolean getUsePolling();
 
     protected LimitLatch initializeConnectionLatch() {
         if (maxConnections==-1) return null;
@@ -1054,8 +1030,8 @@
     }
 
 
-    protected final Set<SocketWrapperBase<S>> waitingRequests = Collections
-            .newSetFromMap(new ConcurrentHashMap<SocketWrapperBase<S>, Boolean>());
+    protected final Set<SocketWrapper<S>> waitingRequests = Collections
+            .newSetFromMap(new ConcurrentHashMap<SocketWrapper<S>, Boolean>());
 
 
     /**
diff --git a/java/org/apache/tomcat/util/net/AprEndpoint.java b/java/org/apache/tomcat/util/net/AprEndpoint.java
index 0aea843..af5f74c 100644
--- a/java/org/apache/tomcat/util/net/AprEndpoint.java
+++ b/java/org/apache/tomcat/util/net/AprEndpoint.java
@@ -16,9 +16,6 @@
  */
 package org.apache.tomcat.util.net;
 
-import java.io.EOFException;
-import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
@@ -26,8 +23,6 @@
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.concurrent.locks.Lock;
-import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
 
 import org.apache.juli.logging.Log;
 import org.apache.juli.logging.LogFactory;
@@ -130,9 +125,9 @@
     /**
      * Handling of accepted sockets.
      */
-    protected Handler<Long> handler = null;
-    public void setHandler(Handler<Long> handler ) { this.handler = handler; }
-    public Handler<Long> getHandler() { return handler; }
+    protected Handler handler = null;
+    public void setHandler(Handler handler ) { this.handler = handler; }
+    public Handler getHandler() { return handler; }
 
 
     /**
@@ -144,6 +139,10 @@
     public void setPollTime(int pollTime) { if (pollTime > 0) { this.pollTime = pollTime; } }
 
 
+    /**
+     * Use sendfile for sending static files.
+     */
+    protected boolean useSendfile = false;
     /*
      * When the endpoint is created and configured, the APR library will not
      * have been initialised. This flag is used to determine if the default
@@ -152,17 +151,25 @@
      * by configuration, that configuration will always take priority.
      */
     private boolean useSendFileSet = false;
-    @Override
     public void setUseSendfile(boolean useSendfile) {
         useSendFileSet = true;
-        super.setUseSendfile(useSendfile);
+        this.useSendfile = useSendfile;
     }
-    /*
-     * For internal use to avoid setting the useSendFileSet flag
+    @Override
+    public boolean getUseSendfile() { return useSendfile; }
+
+
+    /**
+     * Allow comet request handling.
      */
-    private void setUseSendfileInternal(boolean useSendfile) {
-        super.setUseSendfile(useSendfile);
-    }
+    protected boolean useComet = true;
+    public void setUseComet(boolean useComet) { this.useComet = useComet; }
+    @Override
+    public boolean getUseComet() { return useComet; }
+    @Override
+    public boolean getUseCometTimeout() { return false; } // Not supported
+    @Override
+    public boolean getUsePolling() { return true; } // Always supported
 
 
     /**
@@ -464,9 +471,9 @@
         // Enable Sendfile by default if it has not been configured but usage on
         // systems which don't support it cause major problems
         if (!useSendFileSet) {
-            setUseSendfileInternal(Library.APR_HAS_SENDFILE);
-        } else if (getUseSendfile() && !Library.APR_HAS_SENDFILE) {
-            setUseSendfileInternal(false);
+            useSendfile = Library.APR_HAS_SENDFILE;
+        } else if (useSendfile && !Library.APR_HAS_SENDFILE) {
+            useSendfile = false;
         }
 
         // Initialize thread count default for acceptor
@@ -621,8 +628,8 @@
             }
             SSLContext.setVerify(sslContext, value, SSLVerifyDepth);
             // For now, sendfile is not supported with SSL
-            if (getUseSendfile()) {
-                setUseSendfileInternal(false);
+            if (useSendfile) {
+                useSendfile = false;
                 if (useSendFileSet) {
                     log.warn(sm.getString("endpoint.apr.noSendfileWithSSL"));
                 }
@@ -660,7 +667,7 @@
             pollerThread.start();
 
             // Start sendfile thread
-            if (getUseSendfile()) {
+            if (useSendfile) {
                 sendfile = new Sendfile();
                 sendfile.init();
                 Thread sendfileThread =
@@ -726,7 +733,7 @@
             }
             poller = null;
             connections.clear();
-            if (getUseSendfile()) {
+            if (useSendfile) {
                 try {
                     sendfile.destroy();
                 } catch (Exception e) {
@@ -853,7 +860,7 @@
                             Long.valueOf(socket)));
                 }
                 AprSocketWrapper wrapper =
-                        new AprSocketWrapper(Long.valueOf(socket), this);
+                        new AprSocketWrapper(Long.valueOf(socket));
                 wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
                 wrapper.setSecure(isSSLEnabled());
                 connections.put(Long.valueOf(socket), wrapper);
@@ -874,7 +881,8 @@
 
 
     /**
-     * Process given socket. Typically keep alive or upgraded protocol.
+     * Process given socket. Called in non-comet mode, typically keep alive
+     * or upgraded protocol.
      */
     public boolean processSocket(long socket, SocketStatus status) {
         try {
@@ -883,7 +891,7 @@
                 log.warn(sm.getString("endpoint.warn.noExector",
                         Long.valueOf(socket), null));
             } else {
-                SocketWrapperBase<Long> wrapper =
+                SocketWrapper<Long> wrapper =
                         connections.get(Long.valueOf(socket));
                 // Make sure connection hasn't been closed
                 if (wrapper != null) {
@@ -905,7 +913,7 @@
 
 
     @Override
-    public void processSocket(SocketWrapperBase<Long> socket, SocketStatus status,
+    public void processSocket(SocketWrapper<Long> socket, SocketStatus status,
             boolean dispatch) {
         try {
             // Synchronisation is required here as this code may be called as a
@@ -1421,9 +1429,14 @@
             // Close all sockets in the add queue
             SocketInfo info = addList.get();
             while (info != null) {
-                // Poller isn't running at this point so use destroySocket()
-                // directly
-                destroySocket(info.socket);
+                boolean comet =
+                        connections.get(Long.valueOf(info.socket)).isComet();
+                if (!comet || (comet && !processSocket(
+                        info.socket, SocketStatus.STOP))) {
+                    // Poller isn't running at this point so use destroySocket()
+                    // directly
+                    destroySocket(info.socket);
+                }
                 info = addList.get();
             }
             addList.clear();
@@ -1432,7 +1445,12 @@
                 int rv = Poll.pollset(pollers[i], desc);
                 if (rv > 0) {
                     for (int n = 0; n < rv; n++) {
-                        destroySocket(desc[n*2+1]);
+                        boolean comet = connections.get(
+                                Long.valueOf(desc[n*2+1])).isComet();
+                        if (!comet || (comet && !processSocket(
+                                desc[n*2+1], SocketStatus.STOP))) {
+                            destroySocket(desc[n*2+1]);
+                        }
                     }
                 }
             }
@@ -1489,7 +1507,12 @@
             }
             if (!ok) {
                 // Can't do anything: close the socket right away
-                closeSocket(socket);
+                boolean comet = connections.get(
+                        Long.valueOf(socket)).isComet();
+                if (!comet || (comet && !processSocket(
+                        socket, SocketStatus.ERROR))) {
+                    closeSocket(socket);
+                }
             }
         }
 
@@ -1577,7 +1600,12 @@
                             Long.valueOf(socket)));
                 }
                 removeFromPoller(socket);
-                destroySocket(socket);
+                boolean comet = connections.get(
+                        Long.valueOf(socket)).isComet();
+                if (!comet || (comet && !processSocket(
+                        socket, SocketStatus.TIMEOUT))) {
+                    destroySocket(socket);
+                }
                 socket = timeouts.check(date);
             }
 
@@ -1699,11 +1727,20 @@
                                 continue;
                             }
                             if (info.read() || info.write()) {
+                                boolean comet = wrapper.isComet();
+                                if (comet || wrapper.pollerFlags != 0) {
+                                    removeFromPoller(info.socket);
+                                }
                                 wrapper.pollerFlags = wrapper.pollerFlags |
                                         (info.read() ? Poll.APR_POLLIN : 0) |
                                         (info.write() ? Poll.APR_POLLOUT : 0);
                                 if (!addToPoller(info.socket, wrapper.pollerFlags)) {
-                                    closeSocket(info.socket);
+                                    // Can't do anything: close the socket right
+                                    // away
+                                    if (!comet || (comet && !processSocket(
+                                            info.socket, SocketStatus.ERROR))) {
+                                        closeSocket(info.socket);
+                                    }
                                 } else {
                                     timeouts.add(info.socket,
                                             System.currentTimeMillis() +
@@ -1757,7 +1794,42 @@
                                 }
                                 wrapper.pollerFlags = wrapper.pollerFlags & ~((int) desc[n*2]);
                                 // Check for failed sockets and hand this socket off to a worker
-                                if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+                                if (wrapper.isComet()) {
+                                    // Event processes either a read or a write depending on what the poller returns
+                                    if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
+                                            || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
+                                            || ((desc[n*2] & Poll.APR_POLLNVAL) == Poll.APR_POLLNVAL)) {
+                                        if (!processSocket(desc[n*2+1], SocketStatus.ERROR)) {
+                                            // Close socket and clear pool
+                                            closeSocket(desc[n*2+1]);
+                                        }
+                                    } else if ((desc[n*2] & Poll.APR_POLLIN) == Poll.APR_POLLIN) {
+                                        if (wrapper.pollerFlags != 0) {
+                                            add(desc[n*2+1], 1, wrapper.pollerFlags);
+                                        }
+                                        if (!processSocket(desc[n*2+1], SocketStatus.OPEN_READ)) {
+                                            // Close socket and clear pool
+                                            closeSocket(desc[n*2+1]);
+                                        }
+                                    } else if ((desc[n*2] & Poll.APR_POLLOUT) == Poll.APR_POLLOUT) {
+                                        if (wrapper.pollerFlags != 0) {
+                                            add(desc[n*2+1], 1, wrapper.pollerFlags);
+                                        }
+                                        if (!processSocket(desc[n*2+1], SocketStatus.OPEN_WRITE)) {
+                                            // Close socket and clear pool
+                                            closeSocket(desc[n*2+1]);
+                                        }
+                                    } else {
+                                        // Unknown event
+                                        getLog().warn(sm.getString(
+                                                "endpoint.apr.pollUnknownEvent",
+                                                Long.valueOf(desc[n*2])));
+                                        if (!processSocket(desc[n*2+1], SocketStatus.ERROR)) {
+                                            // Close socket and clear pool
+                                            closeSocket(desc[n*2+1]);
+                                        }
+                                    }
+                                } else if (((desc[n*2] & Poll.APR_POLLHUP) == Poll.APR_POLLHUP)
                                         || ((desc[n*2] & Poll.APR_POLLERR) == Poll.APR_POLLERR)
                                         || ((desc[n*2] & Poll.APR_POLLNVAL) == Poll.APR_POLLNVAL)) {
                                     if (wrapper.isAsync() || wrapper.isUpgraded()) {
@@ -2239,6 +2311,19 @@
 
     }
 
+    // ------------------------------------------------ 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 extends AbstractEndpoint.Handler {
+        public SocketState process(SocketWrapper<Long> socket,
+                SocketStatus status);
+    }
+
 
     // --------------------------------- SocketWithOptionsProcessor Inner Class
 
@@ -2251,10 +2336,10 @@
      */
     protected class SocketWithOptionsProcessor implements Runnable {
 
-        protected SocketWrapperBase<Long> socket = null;
+        protected SocketWrapper<Long> socket = null;
 
 
-        public SocketWithOptionsProcessor(SocketWrapperBase<Long> socket) {
+        public SocketWithOptionsProcessor(SocketWrapper<Long> socket) {
             this.socket = socket;
         }
 
@@ -2287,6 +2372,7 @@
                         closeSocket(socket.getSocket().longValue());
                         socket = null;
                     } else if (state == Handler.SocketState.LONG) {
+                        socket.access();
                         if (socket.isAsync()) {
                             waitingRequests.add(socket);
                         }
@@ -2306,10 +2392,10 @@
      */
     protected class SocketProcessor implements Runnable {
 
-        private final SocketWrapperBase<Long> socket;
+        private final SocketWrapper<Long> socket;
         private final SocketStatus status;
 
-        public SocketProcessor(SocketWrapperBase<Long> socket,
+        public SocketProcessor(SocketWrapper<Long> socket,
                 SocketStatus status) {
             this.socket = socket;
             if (status == null) {
@@ -2348,10 +2434,12 @@
                 closeSocket(socket.getSocket().longValue());
                 socket.reset(null, 1);
             } else if (state == Handler.SocketState.LONG) {
+                socket.access();
                 if (socket.isAsync()) {
                     waitingRequests.add(socket);
                 }
             } else if (state == Handler.SocketState.ASYNC_END) {
+                socket.access();
                 SocketProcessor proc = new SocketProcessor(socket,
                         SocketStatus.OPEN_READ);
                 getExecutor().execute(proc);
@@ -2360,261 +2448,13 @@
     }
 
 
-    public static class AprSocketWrapper extends SocketWrapperBase<Long> {
-
-        private static final int SSL_OUTPUT_BUFFER_SIZE = 8192;
-
-        private final ByteBuffer sslOutputBuffer;
-
-        private volatile ByteBuffer returnedInput;
-        private volatile boolean eagain = false;
-        private volatile boolean closed = false;
+    private static class AprSocketWrapper extends SocketWrapper<Long> {
 
         // This field should only be used by Poller#run()
         private int pollerFlags = 0;
 
-
-        public AprSocketWrapper(Long socket, AprEndpoint endpoint) {
-            super(socket, endpoint);
-
-            if (endpoint.isSSLEnabled()) {
-                sslOutputBuffer = ByteBuffer.allocateDirect(SSL_OUTPUT_BUFFER_SIZE);
-                sslOutputBuffer.position(SSL_OUTPUT_BUFFER_SIZE);
-            } else {
-                sslOutputBuffer = null;
-            }
-        }
-
-
-        // TODO Can this be removed once all reads and writes are handled within
-        // this class?
-        @Override
-        public void setTimeout(long timeout) {
-            super.setTimeout(timeout);
-            Socket.timeoutSet(getSocket().longValue(), timeout * 1000);
-        }
-
-
-
-        @Override
-        public int read(boolean block, byte[] b, int off, int len) throws IOException {
-
-            if (closed) {
-                throw new IOException(sm.getString("socket.apr.closed", getSocket()));
-            }
-
-            if (returnedInput != null) {
-                if (returnedInput.remaining() < len) {
-                    len = returnedInput.remaining();
-                }
-                returnedInput.get(b, off, len);
-                if (returnedInput.remaining() == 0) {
-                    returnedInput = null;
-                }
-                return len;
-            }
-
-            Lock readLock = getBlockingStatusReadLock();
-            WriteLock writeLock = getBlockingStatusWriteLock();
-
-            boolean readDone = false;
-            int result = 0;
-            readLock.lock();
-            try {
-                if (getBlockingStatus() == block) {
-                    result = Socket.recv(getSocket().longValue(), b, off, len);
-                    readDone = true;
-                }
-            } finally {
-                readLock.unlock();
-            }
-
-            if (!readDone) {
-                writeLock.lock();
-                try {
-                    setBlockingStatus(block);
-                    // Set the current settings for this socket
-                    Socket.optSet(getSocket().longValue(), Socket.APR_SO_NONBLOCK, (block ? 0 : 1));
-                    // Downgrade the lock
-                    readLock.lock();
-                    try {
-                        writeLock.unlock();
-                        result = Socket.recv(getSocket().longValue(), b, off, len);
-                    } finally {
-                        readLock.unlock();
-                    }
-                } finally {
-                    // Should have been released above but may not have been on some
-                    // exception paths
-                    if (writeLock.isHeldByCurrentThread()) {
-                        writeLock.unlock();
-                    }
-                }
-            }
-
-            if (result > 0) {
-                eagain = false;
-                return result;
-            } else if (-result == Status.EAGAIN) {
-                eagain = true;
-                return 0;
-            } else if (-result == Status.APR_EGENERAL && isSecure()) {
-                // Not entirely sure why this is necessary. Testing to date has not
-                // identified any issues with this but log it so it can be tracked
-                // if it is suspected of causing issues in the future.
-                if (log.isDebugEnabled()) {
-                    log.debug(sm.getString("socket.apr.read.sslGeneralError", getSocket(), this));
-                }
-                eagain = true;
-                return 0;
-            } else if (-result == Status.APR_EOF) {
-                throw new EOFException(sm.getString("socket.apr.clientAbort"));
-            } else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
-                    (-result == Status.APR_OS_START_SYSERR + 10053)) {
-                // 10053 on Windows is connection aborted
-                throw new EOFException(sm.getString("socket.apr.clientAbort"));
-            } else {
-                throw new IOException(sm.getString("socket.apr.read.error",
-                        Integer.valueOf(-result), getSocket(), this));
-            }
-        }
-
-
-        @Override
-        public boolean isReady() {
-            return !eagain;
-        }
-
-
-
-        @Override
-        public void unRead(ByteBuffer input) {
-            if (returnedInput != null) {
-                this.returnedInput = ByteBuffer.allocate(returnedInput.remaining());
-                this.returnedInput.put(returnedInput);
-            }
-        }
-
-
-        @Override
-        public void close() {
-            closed = true;
-            // AbstractProcessor needs to trigger the close as multiple closes for
-            // APR/native sockets will cause problems.
-        }
-
-
-        @Override
-        public int write(boolean block, byte[] b, int off, int len) throws IOException {
-
-            if (closed) {
-                throw new IOException(sm.getString("apr.closed", getSocket()));
-            }
-
-            Lock readLock = getBlockingStatusReadLock();
-            WriteLock writeLock = getBlockingStatusWriteLock();
-
-            readLock.lock();
-            try {
-                if (getBlockingStatus() == block) {
-                    return doWriteInternal(b, off, len);
-                }
-            } finally {
-                readLock.unlock();
-            }
-
-            writeLock.lock();
-            try {
-                // Set the current settings for this socket
-                setBlockingStatus(block);
-                if (block) {
-                    Socket.timeoutSet(getSocket().longValue(), getEndpoint().getSoTimeout() * 1000);
-                } else {
-                    Socket.timeoutSet(getSocket().longValue(), 0);
-                }
-
-                // Downgrade the lock
-                readLock.lock();
-                try {
-                    writeLock.unlock();
-                    return doWriteInternal(b, off, len);
-                } finally {
-                    readLock.unlock();
-                }
-            } finally {
-                // Should have been released above but may not have been on some
-                // exception paths
-                if (writeLock.isHeldByCurrentThread()) {
-                    writeLock.unlock();
-                }
-            }
-        }
-
-
-        private int doWriteInternal(byte[] b, int off, int len) throws IOException {
-
-            int start = off;
-            int left = len;
-            int written;
-
-            do {
-                if (getEndpoint().isSSLEnabled()) {
-                    if (sslOutputBuffer.remaining() == 0) {
-                        // Buffer was fully written last time around
-                        sslOutputBuffer.clear();
-                        if (left < SSL_OUTPUT_BUFFER_SIZE) {
-                            sslOutputBuffer.put(b, start, left);
-                        } else {
-                            sslOutputBuffer.put(b, start, SSL_OUTPUT_BUFFER_SIZE);
-                        }
-                        sslOutputBuffer.flip();
-                    } else {
-                        // Buffer still has data from previous attempt to write
-                        // APR + SSL requires that exactly the same parameters are
-                        // passed when re-attempting the write
-                    }
-                    written = Socket.sendb(getSocket().longValue(), sslOutputBuffer,
-                            sslOutputBuffer.position(), sslOutputBuffer.limit());
-                    if (written > 0) {
-                        sslOutputBuffer.position(
-                                sslOutputBuffer.position() + written);
-                    }
-                } else {
-                    written = Socket.send(getSocket().longValue(), b, start, left);
-                }
-                if (Status.APR_STATUS_IS_EAGAIN(-written)) {
-                    written = 0;
-                } else if (-written == Status.APR_EOF) {
-                    throw new EOFException(sm.getString("apr.clientAbort"));
-                } else if ((OS.IS_WIN32 || OS.IS_WIN64) &&
-                        (-written == Status.APR_OS_START_SYSERR + 10053)) {
-                    // 10053 on Windows is connection aborted
-                    throw new EOFException(sm.getString("apr.clientAbort"));
-                } else if (written < 0) {
-                    throw new IOException(sm.getString("apr.write.error",
-                            Integer.valueOf(-written), getSocket(), this));
-                }
-                start += written;
-                left -= written;
-            } while (written > 0 && left > 0);
-
-            if (left > 0) {
-                ((AprEndpoint) getEndpoint()).getPoller().add(getSocket().longValue(), -1, false, true);
-            }
-            return len - left;
-        }
-
-
-        @Override
-        public void flush() {
-            // NO-OP
-        }
-
-
-        @Override
-        public void regsiterForEvent(boolean read, boolean write) {
-            ((AprEndpoint) getEndpoint()).getPoller().add(
-                    getSocket().longValue(), -1, read, write);
+        public AprSocketWrapper(Long socket) {
+            super(socket);
         }
     }
 }
diff --git a/java/org/apache/tomcat/util/net/Constants.java b/java/org/apache/tomcat/util/net/Constants.java
index 769e345..67ed493 100644
--- a/java/org/apache/tomcat/util/net/Constants.java
+++ b/java/org/apache/tomcat/util/net/Constants.java
@@ -23,4 +23,11 @@
      * the tomcat instance installation path
      */
     public static final String CATALINA_BASE_PROP = "catalina.base";
+
+
+    /**
+     * Has security been turned on?
+     */
+    public static final boolean IS_SECURITY_ENABLED =
+        (System.getSecurityManager() != null);
 }
diff --git a/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java b/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
new file mode 100644
index 0000000..497cfaa
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/DefaultServerSocketFactory.java
@@ -0,0 +1,67 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * Default server socket factory. Doesn't do much except give us
+ * plain old server sockets.
+ *
+ * @author db@eng.sun.com
+ * @author Harish Prabandham
+ */
+public class DefaultServerSocketFactory implements ServerSocketFactory {
+
+    /**
+     *
+     * @param endpoint  Unused in this implementation.
+     */
+    public DefaultServerSocketFactory(AbstractEndpoint<?> endpoint) {
+    }
+
+    @Override
+    public ServerSocket createSocket (int port) throws IOException {
+        return  new ServerSocket (port);
+    }
+
+    @Override
+    public ServerSocket createSocket (int port, int backlog)
+            throws IOException {
+        return new ServerSocket (port, backlog);
+    }
+
+    @Override
+    public ServerSocket createSocket (int port, int backlog,
+            InetAddress ifAddress) throws IOException {
+        return new ServerSocket (port, backlog, ifAddress);
+    }
+
+    @Override
+    public Socket acceptSocket(ServerSocket socket) throws IOException {
+        return socket.accept();
+    }
+
+    @Override
+    public void handshake(Socket sock) throws IOException {
+        // NOOP
+    }
+}
diff --git a/java/org/apache/tomcat/util/net/JIoEndpoint.java b/java/org/apache/tomcat/util/net/JIoEndpoint.java
new file mode 100644
index 0000000..749af96
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/JIoEndpoint.java
@@ -0,0 +1,545 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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;
+import java.net.BindException;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.apache.tomcat.util.ExceptionUtils;
+import org.apache.tomcat.util.net.AbstractEndpoint.Handler.SocketState;
+import org.apache.tomcat.util.net.jsse.JSSESocketFactory;
+
+
+/**
+ * Handle incoming TCP connections.
+ *
+ * This class implement a simple server model: one listener thread accepts on a socket and
+ * creates a new worker thread for each incoming connection.
+ *
+ * More advanced Endpoints will reuse the threads, use queues, etc.
+ *
+ * @author James Duncan Davidson
+ * @author Jason Hunter
+ * @author James Todd
+ * @author Costin Manolache
+ * @author Gal Shachor
+ * @author Yoav Shapira
+ * @author Remy Maucherat
+ */
+public class JIoEndpoint extends AbstractEndpoint<Socket> {
+
+
+    // -------------------------------------------------------------- Constants
+
+    private static final Log log = LogFactory.getLog(JIoEndpoint.class);
+
+    // ----------------------------------------------------------------- Fields
+
+    /**
+     * Associated server socket.
+     */
+    protected ServerSocket serverSocket = null;
+
+
+    // ------------------------------------------------------------ Constructor
+
+    public JIoEndpoint() {
+        // Set maxConnections to zero so we can tell if the user has specified
+        // their own value on the connector when we reach bind()
+        setMaxConnections(0);
+        // Reduce the executor timeout for BIO as threads in keep-alive will not
+        // terminate when the executor interrupts them.
+        setExecutorTerminationTimeoutMillis(0);
+    }
+
+    // ------------------------------------------------------------- Properties
+
+    /**
+     * Handling of accepted sockets.
+     */
+    protected Handler handler = null;
+    public void setHandler(Handler handler ) { this.handler = handler; }
+    public Handler getHandler() { return handler; }
+
+    /**
+     * Server socket factory.
+     */
+    protected ServerSocketFactory serverSocketFactory = null;
+    public void setServerSocketFactory(ServerSocketFactory factory) { this.serverSocketFactory = factory; }
+    public ServerSocketFactory getServerSocketFactory() { return serverSocketFactory; }
+
+    /**
+     * Port in use.
+     */
+    @Override
+    public int getLocalPort() {
+        ServerSocket s = serverSocket;
+        if (s == null) {
+            return -1;
+        } else {
+            return s.getLocalPort();
+        }
+    }
+
+
+    @Override
+    public String[] getCiphersUsed() {
+        if (serverSocketFactory instanceof JSSESocketFactory) {
+            return ((JSSESocketFactory) serverSocketFactory).getEnabledCiphers();
+        }
+        return new String[0];
+    }
+
+
+    /*
+     * Optional feature support.
+     */
+    @Override
+    public boolean getUseSendfile() { return false; } // Not supported
+    @Override
+    public boolean getUseComet() { return false; } // Not supported
+    @Override
+    public boolean getUseCometTimeout() { return false; } // Not supported
+    @Override
+    public boolean getDeferAccept() { return false; } // Not supported
+    @Override
+    public boolean getUsePolling() { return false; } // Not supported
+
+
+    // ------------------------------------------------ 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 extends AbstractEndpoint.Handler {
+        public SocketState process(SocketWrapper<Socket> socket,
+                SocketStatus status);
+        public SSLImplementation getSslImplementation();
+        public void beforeHandshake(SocketWrapper<Socket> socket);
+    }
+
+
+    // --------------------------------------------------- Acceptor Inner Class
+    /**
+     * The background thread that listens for incoming TCP/IP connections and
+     * hands them off to an appropriate processor.
+     */
+    protected class Acceptor extends AbstractEndpoint.Acceptor {
+
+        @Override
+        public void run() {
+
+            int errorDelay = 0;
+
+            // Loop until we receive a shutdown command
+            while (running) {
+
+                // Loop if endpoint is paused
+                while (paused && running) {
+                    state = AcceptorState.PAUSED;
+                    try {
+                        Thread.sleep(50);
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                if (!running) {
+                    break;
+                }
+                state = AcceptorState.RUNNING;
+
+                try {
+                    //if we have reached max connections, wait
+                    countUpOrAwaitConnection();
+
+                    Socket socket = null;
+                    try {
+                        // Accept the next incoming connection from the server
+                        // socket
+                        socket = serverSocketFactory.acceptSocket(serverSocket);
+                    } catch (IOException ioe) {
+                        countDownConnection();
+                        // Introduce delay if necessary
+                        errorDelay = handleExceptionWithDelay(errorDelay);
+                        // re-throw
+                        throw ioe;
+                    }
+                    // Successful accept, reset the error delay
+                    errorDelay = 0;
+
+                    // Configure the socket
+                    if (running && !paused && setSocketOptions(socket)) {
+                        // Hand this socket off to an appropriate processor
+                        if (!processSocket(socket)) {
+                            countDownConnection();
+                            // Close socket right away
+                            closeSocket(socket);
+                        }
+                    } else {
+                        countDownConnection();
+                        // Close socket right away
+                        closeSocket(socket);
+                    }
+                } catch (IOException x) {
+                    if (running) {
+                        log.error(sm.getString("endpoint.accept.fail"), x);
+                    }
+                } catch (NullPointerException npe) {
+                    if (running) {
+                        log.error(sm.getString("endpoint.accept.fail"), npe);
+                    }
+                } catch (Throwable t) {
+                    ExceptionUtils.handleThrowable(t);
+                    log.error(sm.getString("endpoint.accept.fail"), t);
+                }
+            }
+            state = AcceptorState.ENDED;
+        }
+    }
+
+
+    private void closeSocket(Socket socket) {
+        try {
+            socket.close();
+        } catch (IOException e) {
+            // Ignore
+        }
+    }
+
+
+    // ------------------------------------------- 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 SocketWrapper<Socket> socket = null;
+        protected SocketStatus status = null;
+
+        public SocketProcessor(SocketWrapper<Socket> socket) {
+            if (socket==null) throw new NullPointerException();
+            this.socket = socket;
+        }
+
+        public SocketProcessor(SocketWrapper<Socket> socket, SocketStatus status) {
+            this(socket);
+            this.status = status;
+        }
+
+        @Override
+        public void run() {
+            boolean launch = false;
+            synchronized (socket) {
+                try {
+                    SocketState state = SocketState.OPEN;
+                    handler.beforeHandshake(socket);
+                    try {
+                        // SSL handshake
+                        serverSocketFactory.handshake(socket.getSocket());
+                    } catch (Throwable t) {
+                        ExceptionUtils.handleThrowable(t);
+                        if (log.isDebugEnabled()) {
+                            log.debug(sm.getString("endpoint.err.handshake"), t);
+                        }
+                        // Tell to close the socket
+                        state = SocketState.CLOSED;
+                    }
+
+                    if ((state != SocketState.CLOSED)) {
+                        if (status == null) {
+                            state = handler.process(socket, SocketStatus.OPEN_READ);
+                        } else {
+                            state = handler.process(socket,status);
+                        }
+                    }
+                    if (state == SocketState.CLOSED) {
+                        // Close socket
+                        if (log.isTraceEnabled()) {
+                            log.trace("Closing socket:"+socket);
+                        }
+                        countDownConnection();
+                        try {
+                            socket.getSocket().close();
+                        } catch (IOException e) {
+                            // Ignore
+                        }
+                    } else if (state == SocketState.OPEN ||
+                            state == SocketState.UPGRADING  ||
+                            state == SocketState.UPGRADED){
+                        socket.setKeptAlive(true);
+                        socket.access();
+                        launch = true;
+                    } else if (state == SocketState.LONG) {
+                        socket.access();
+                        waitingRequests.add(socket);
+                    }
+                } finally {
+                    if (launch) {
+                        try {
+                            getExecutor().execute(new SocketProcessor(socket, SocketStatus.OPEN_READ));
+                        } catch (RejectedExecutionException x) {
+                            log.warn("Socket reprocessing request was rejected for:"+socket,x);
+                            try {
+                                //unable to handle connection at this time
+                                handler.process(socket, SocketStatus.DISCONNECT);
+                            } finally {
+                                countDownConnection();
+                            }
+
+
+                        } catch (NullPointerException npe) {
+                            if (running) {
+                                log.error(sm.getString("endpoint.launch.fail"),
+                                        npe);
+                            }
+                        }
+                    }
+                }
+            }
+            socket = null;
+            // Finish up this request
+        }
+
+    }
+
+
+    // -------------------- Public methods --------------------
+
+    @Override
+    public void bind() throws Exception {
+
+        // Initialize thread count defaults for acceptor
+        if (acceptorThreadCount == 0) {
+            acceptorThreadCount = 1;
+        }
+        // Initialize maxConnections
+        if (getMaxConnections() == 0) {
+            // User hasn't set a value - use the default
+            setMaxConnections(getMaxThreadsExecutor(true));
+        }
+
+        if (serverSocketFactory == null) {
+            if (isSSLEnabled()) {
+                serverSocketFactory =
+                    handler.getSslImplementation().getServerSocketFactory(this);
+            } else {
+                serverSocketFactory = new DefaultServerSocketFactory(this);
+            }
+        }
+
+        if (serverSocket == null) {
+            try {
+                if (getAddress() == null) {
+                    serverSocket = serverSocketFactory.createSocket(getPort(),
+                            getBacklog());
+                } else {
+                    serverSocket = serverSocketFactory.createSocket(getPort(),
+                            getBacklog(), getAddress());
+                }
+            } catch (BindException orig) {
+                String msg;
+                if (getAddress() == null)
+                    msg = orig.getMessage() + " <null>:" + getPort();
+                else
+                    msg = orig.getMessage() + " " +
+                            getAddress().toString() + ":" + getPort();
+                BindException be = new BindException(msg);
+                be.initCause(orig);
+                throw be;
+            }
+        }
+
+    }
+
+    @Override
+    public void startInternal() throws Exception {
+
+        if (!running) {
+            running = true;
+            paused = false;
+
+            // Create worker collection
+            if (getExecutor() == null) {
+                createExecutor();
+            }
+
+            initializeConnectionLatch();
+
+            startAcceptorThreads();
+
+            // Start async timeout thread
+            setAsyncTimeout(new AsyncTimeout());
+            Thread timeoutThread = new Thread(getAsyncTimeout(), getName() + "-AsyncTimeout");
+            timeoutThread.setPriority(threadPriority);
+            timeoutThread.setDaemon(true);
+            timeoutThread.start();
+        }
+    }
+
+    @Override
+    public void stopInternal() {
+        releaseConnectionLatch();
+        if (!paused) {
+            pause();
+        }
+        if (running) {
+            running = false;
+            getAsyncTimeout().stop();
+            unlockAccept();
+        }
+        shutdownExecutor();
+    }
+
+    /**
+     * Deallocate APR memory pools, and close server socket.
+     */
+    @Override
+    public void unbind() throws Exception {
+        if (running) {
+            stop();
+        }
+        if (serverSocket != null) {
+            try {
+                if (serverSocket != null)
+                    serverSocket.close();
+            } catch (Exception e) {
+                log.error(sm.getString("endpoint.err.close"), e);
+            }
+            serverSocket = null;
+        }
+        handler.recycle();
+    }
+
+
+    @Override
+    protected AbstractEndpoint.Acceptor createAcceptor() {
+        return new Acceptor();
+    }
+
+
+    /**
+     * Configure the socket.
+     */
+    protected boolean setSocketOptions(Socket socket) {
+        try {
+            // 1: Set socket options: timeout, linger, etc
+            socketProperties.setProperties(socket);
+        } catch (SocketException s) {
+            //error here is common if the client has reset the connection
+            if (log.isDebugEnabled()) {
+                log.debug(sm.getString("endpoint.err.unexpected"), s);
+            }
+            // Close the socket
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(t);
+            log.error(sm.getString("endpoint.err.unexpected"), t);
+            // Close the socket
+            return false;
+        }
+        return true;
+    }
+
+
+    /**
+     * Process a new connection from a new client. Wraps the socket so
+     * keep-alive and other attributes can be tracked and then passes the socket
+     * to the executor for processing.
+     *
+     * @param socket    The socket associated with the client.
+     *
+     * @return          <code>true</code> if the socket is passed to the
+     *                  executor, <code>false</code> if something went wrong or
+     *                  if the endpoint is shutting down. Returning
+     *                  <code>false</code> is an indication to close the socket
+     *                  immediately.
+     */
+    protected boolean processSocket(Socket socket) {
+        // Process the request from this socket
+        try {
+            SocketWrapper<Socket> wrapper = new SocketWrapper<>(socket);
+            wrapper.setKeepAliveLeft(getMaxKeepAliveRequests());
+            wrapper.setSecure(isSSLEnabled());
+            // During shutdown, executor may be null - avoid NPE
+            if (!running) {
+                return false;
+            }
+            getExecutor().execute(new SocketProcessor(wrapper));
+        } catch (RejectedExecutionException x) {
+            log.warn("Socket processing request was rejected for:"+socket,x);
+            return false;
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(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;
+    }
+
+
+    @Override
+    public void processSocket(SocketWrapper<Socket> socket,
+            SocketStatus status, boolean dispatch) {
+        try {
+            // Synchronisation is required here as this code may be called as a
+            // result of calling AsyncContext.dispatch() from a non-container
+            // thread
+            synchronized (socket) {
+                if (waitingRequests.remove(socket)) {
+                    SocketProcessor proc = new SocketProcessor(socket,status);
+                    Executor executor = getExecutor();
+                    if (dispatch && executor != null) {
+                        // During shutdown, executor may be null - avoid NPE
+                        if (!running) {
+                            return;
+                        }
+                        getExecutor().execute(proc);
+                    } else {
+                        proc.run();
+                    }
+                }
+            }
+        } catch (RejectedExecutionException ree) {
+            log.warn(sm.getString("endpoint.executor.fail", socket) , ree);
+        } catch (Throwable t) {
+            ExceptionUtils.handleThrowable(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);
+        }
+    }
+
+    @Override
+    protected Log getLog() {
+        return log;
+    }
+}
diff --git a/java/org/apache/tomcat/util/net/Nio2Channel.java b/java/org/apache/tomcat/util/net/Nio2Channel.java
index d740c3d..7b44936 100644
--- a/java/org/apache/tomcat/util/net/Nio2Channel.java
+++ b/java/org/apache/tomcat/util/net/Nio2Channel.java
@@ -38,7 +38,7 @@
     protected static ByteBuffer emptyBuf = ByteBuffer.allocate(0);
 
     protected AsynchronousSocketChannel sc = null;
-    protected SocketWrapperBase<Nio2Channel> socket = null;
+    protected SocketWrapper<Nio2Channel> socket = null;
     protected ApplicationBufferHandler bufHandler;
 
     public Nio2Channel(ApplicationBufferHandler bufHandler) {
@@ -50,7 +50,7 @@
      *
      * @throws IOException If a problem was encountered resetting the channel
      */
-    public void reset(AsynchronousSocketChannel channel, SocketWrapperBase<Nio2Channel> socket)
+    public void reset(AsynchronousSocketChannel channel, SocketWrapper<Nio2Channel> socket)
             throws IOException {
         this.sc = channel;
         this.socket = socket;
@@ -58,7 +58,7 @@
         bufHandler.getWriteBuffer().clear();
     }
 
-    public SocketWrapperBase<Nio2Channel> getSocket() {
+    public SocketWrapper<Nio2Channel> getSocket() {
         return socket;
     }
 
diff --git a/java/org/apache/tomcat/util/net/Nio2Endpoint.java b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
index 5918257..0f9a023 100644
--- a/java/org/apache/tomcat/util/net/Nio2Endpoint.java
+++ b/java/org/apache/tomcat/util/net/Nio2Endpoint.java
@@ -22,23 +22,18 @@
 import java.io.IOException;
 import java.net.InetSocketAddress;
 import java.net.SocketAddress;
-import java.net.SocketTimeoutException;
 import java.nio.ByteBuffer;
 import java.nio.channels.AsynchronousChannelGroup;
-import java.nio.channels.AsynchronousCloseException;
 import java.nio.channels.AsynchronousServerSocketChannel;
 import java.nio.channels.AsynchronousSocketChannel;
 import java.nio.channels.ClosedChannelException;
 import java.nio.channels.CompletionHandler;
 import java.nio.channels.FileChannel;
 import java.nio.file.StandardOpenOption;
-import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.RejectedExecutionException;
-import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
 
 import javax.net.ssl.KeyManager;
 import javax.net.ssl.SSLContext;
@@ -74,6 +69,11 @@
     private AsynchronousServerSocketChannel serverSock = null;
 
     /**
+     * use send file
+     */
+    private boolean useSendfile = true;
+
+    /**
      * The size of the OOM parachute.
      */
     private int oomParachute = 1024*1024;
@@ -136,6 +136,14 @@
 
 
     /**
+     * Priority of the poller threads.
+     */
+    private int pollerThreadPriority = Thread.NORM_PRIORITY;
+    public void setPollerThreadPriority(int pollerThreadPriority) { this.pollerThreadPriority = pollerThreadPriority; }
+    public int getPollerThreadPriority() { return pollerThreadPriority; }
+
+
+    /**
      * Handling of accepted sockets.
      */
     private Handler handler = null;
@@ -143,10 +151,26 @@
     public Handler getHandler() { return handler; }
 
 
+    /**
+     * Allow comet request handling.
+     */
+    private boolean useComet = true;
+    public void setUseComet(boolean useComet) { this.useComet = useComet; }
+    @Override
+    public boolean getUseComet() { return useComet; }
+    @Override
+    public boolean getUseCometTimeout() { return getUseComet(); }
+    @Override
+    public boolean getUsePolling() { return true; } // Always supported
+
     public void setSocketProperties(SocketProperties socketProperties) {
         this.socketProperties = socketProperties;
     }
 
+    public void setUseSendfile(boolean useSendfile) {
+        this.useSendfile = useSendfile;
+    }
+
     /**
      * Is deferAccept supported?
      */
@@ -182,7 +206,7 @@
         } else {
             try {
                 SocketAddress sa = ssc.getLocalAddress();
-                if (sa instanceof InetSocketAddress) {
+                if (sa != null && sa instanceof InetSocketAddress) {
                     return ((InetSocketAddress) sa).getPort();
                 } else {
                     return -1;
@@ -366,7 +390,7 @@
                 @Override
                 public void run() {
                     // Timeout any pending async request
-                    for (SocketWrapperBase<Nio2Channel> socket : waitingRequests) {
+                    for (SocketWrapper<Nio2Channel> socket : waitingRequests) {
                         processSocket(socket, SocketStatus.TIMEOUT, false);
                     }
                     // Then close all active connections if any remains
@@ -445,6 +469,11 @@
         return socketProperties.getRxBufSize();
     }
 
+    @Override
+    public boolean getUseSendfile() {
+        return useSendfile;
+    }
+
     public int getOomParachute() {
         return oomParachute;
     }
@@ -494,7 +523,7 @@
             }
             Nio2SocketWrapper socketWrapper = (useCaches) ? socketWrapperCache.pop() : null;
             if (socketWrapper == null) {
-                socketWrapper = new Nio2SocketWrapper(channel, this);
+                socketWrapper = new Nio2SocketWrapper(channel);
             }
             channel.reset(socket, socketWrapper);
             socketWrapper.reset(channel, getSocketProperties().getSoTimeout());
@@ -549,12 +578,12 @@
 
 
     @Override
-    public void processSocket(SocketWrapperBase<Nio2Channel> socketWrapper,
+    public void processSocket(SocketWrapper<Nio2Channel> socketWrapper,
             SocketStatus socketStatus, boolean dispatch) {
         processSocket0(socketWrapper, socketStatus, dispatch);
     }
 
-    protected boolean processSocket0(SocketWrapperBase<Nio2Channel> socketWrapper, SocketStatus status, boolean dispatch) {
+    protected boolean processSocket0(SocketWrapper<Nio2Channel> socketWrapper, SocketStatus status, boolean dispatch) {
         try {
             SocketProcessor sc = (useCaches) ? processorCache.pop() : null;
             if (sc == null) {
@@ -581,11 +610,22 @@
         return true;
     }
 
-    public void closeSocket(SocketWrapperBase<Nio2Channel> socket) {
+    public void closeSocket(SocketWrapper<Nio2Channel> socket, SocketStatus status) {
         if (socket == null) {
             return;
         }
         try {
+            if (socket.isComet() && status != null) {
+                socket.setComet(false);//to avoid a loop
+                if (status == SocketStatus.TIMEOUT) {
+                    if (processSocket0(socket, status, true)) {
+                        return; // don't close on comet timeout
+                    }
+                } else {
+                    // Don't dispatch if the lines below are canceling the key
+                    processSocket0(socket, status, false);
+                }
+            }
             handler.release(socket);
             try {
                 if (socket.getSocket() != null) {
@@ -710,86 +750,13 @@
     }
 
 
-    public static class Nio2SocketWrapper extends SocketWrapperBase<Nio2Channel> {
+    public static class Nio2SocketWrapper extends SocketWrapper<Nio2Channel> {
 
         private SendfileData sendfileData = null;
         private boolean upgradeInit = false;
 
-        private final CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> readCompletionHandler;
-        private boolean flipped = false;
-        private volatile boolean readPending = false;
-        private volatile boolean interest = true;
-
-        private final int maxWrite;
-        private final CompletionHandler<Integer, ByteBuffer> writeCompletionHandler;
-        private final Semaphore writePending = new Semaphore(1);
-
-
-        public Nio2SocketWrapper(Nio2Channel channel, Nio2Endpoint endpoint) {
-            super(channel, endpoint);
-            maxWrite = channel.getBufHandler().getWriteBuffer().capacity();
-
-            this.readCompletionHandler = new CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>>() {
-                @Override
-                public void completed(Integer nBytes, SocketWrapperBase<Nio2Channel> attachment) {
-                    boolean notify = false;
-                    if (log.isDebugEnabled()) {
-                        log.debug("Socket: [ + " + attachment + "], Interest: [" + interest + "]");
-                    }
-                    synchronized (readCompletionHandler) {
-                        if (nBytes.intValue() < 0) {
-                            failed(new EOFException(), attachment);
-                        } else {
-                            readPending = false;
-                            if (interest && !Nio2Endpoint.isInline()) {
-                                interest = false;
-                                notify = true;
-                            }
-                        }
-                    }
-                    if (notify) {
-                        getEndpoint().processSocket(attachment, SocketStatus.OPEN_READ, false);
-                    }
-                }
-                @Override
-                public void failed(Throwable exc, SocketWrapperBase<Nio2Channel> attachment) {
-                    attachment.setError(true);
-                    readPending = false;
-                    if (exc instanceof AsynchronousCloseException) {
-                        // If already closed, don't call onError and close again
-                        return;
-                    }
-                    getEndpoint().processSocket(attachment, SocketStatus.ERROR, true);
-                }
-            };
-
-            this.writeCompletionHandler = new CompletionHandler<Integer, ByteBuffer>() {
-                @Override
-                public void completed(Integer nBytes, ByteBuffer attachment) {
-                    if (nBytes.intValue() < 0) {
-                        failed(new EOFException(), attachment);
-                    } else if (attachment.hasRemaining()) {
-                        getSocket().write(attachment, getTimeout(),
-                                TimeUnit.MILLISECONDS, attachment, writeCompletionHandler);
-                    } else {
-                        writePending.release();
-                        if (!Nio2Endpoint.isInline()) {
-                            getEndpoint().processSocket(Nio2SocketWrapper.this, SocketStatus.OPEN_WRITE, false);
-                        }
-                    }
-                }
-                @Override
-                public void failed(Throwable exc, ByteBuffer attachment) {
-                    setError(true);
-                    writePending.release();
-                    if (exc instanceof AsynchronousCloseException) {
-                        // If already closed, don't call onError and close again
-                        return;
-                    }
-                    getEndpoint().processSocket(Nio2SocketWrapper.this, SocketStatus.ERROR, true);
-                }
-            };
-
+        public Nio2SocketWrapper(Nio2Channel channel) {
+            super(channel);
         }
 
         @Override
@@ -822,281 +789,8 @@
         public void setSendfileData(SendfileData sf) { this.sendfileData = sf; }
         public SendfileData getSendfileData() { return this.sendfileData; }
 
-
-        @Override
-        public boolean isReady() throws IOException {
-            synchronized (readCompletionHandler) {
-                if (readPending) {
-                    interest = true;
-                    return false;
-                }
-                ByteBuffer readBuffer = getSocket().getBufHandler().getReadBuffer();
-                if (!flipped) {
-                    readBuffer.flip();
-                    flipped = true;
-                }
-                if (readBuffer.remaining() > 0) {
-                    return true;
-                }
-
-                readBuffer.clear();
-                flipped = false;
-                int nRead = fillReadBuffer(false);
-
-                boolean isReady = nRead > 0;
-                if (isReady) {
-                    if (!flipped) {
-                        readBuffer.flip();
-                        flipped = true;
-                    }
-                } else {
-                    interest = true;
-                }
-                return isReady;
-            }
-        }
-
-
-        @Override
-        public int read(boolean block, byte[] b, int off, int len) throws IOException {
-
-            if (log.isDebugEnabled()) {
-                log.debug("Socket: [" + this + "], block: [" + block + "], length: [" + len + "]");
-            }
-
-            synchronized (readCompletionHandler) {
-                if (readPending) {
-                    if (log.isDebugEnabled()) {
-                        log.debug("Socket: [" + this + "], Read: [0]");
-                    }
-                    return 0;
-                }
-
-                ByteBuffer readBuffer = getSocket().getBufHandler().getReadBuffer();
-
-                if (!flipped) {
-                    readBuffer.flip();
-                    flipped = true;
-                }
-                int remaining = readBuffer.remaining();
-                // Is there enough data in the read buffer to satisfy this request?
-                if (remaining >= len) {
-                    readBuffer.get(b, off, len);
-                    if (log.isDebugEnabled()) {
-                        log.debug("Socket: [" + this + "], Read from buffer: [" + len + "]");
-                    }
-                    return len;
-                }
-
-                // Copy what data there is in the read buffer to the byte array
-                int leftToWrite = len;
-                int newOffset = off;
-                if (remaining > 0) {
-                    readBuffer.get(b, off, remaining);
-                    leftToWrite -= remaining;
-                    newOffset += remaining;
-                }
-
-                // Fill the read buffer as best we can
-                readBuffer.clear();
-                flipped = false;
-                int nRead = fillReadBuffer(block);
-
-                // Full as much of the remaining byte array as possible with the data
-                // that was just read
-                if (nRead > 0) {
-                    if (!flipped) {
-                        readBuffer.flip();
-                        flipped = true;
-                    }
-                    if (nRead > leftToWrite) {
-                        readBuffer.get(b, newOffset, leftToWrite);
-                        leftToWrite = 0;
-                    } else {
-                        readBuffer.get(b, newOffset, nRead);
-                        leftToWrite -= nRead;
-                    }
-                } else if (nRead == 0) {
-                    if (block) {
-                        if (!flipped) {
-                            readBuffer.flip();
-                            flipped = true;
-                        }
-                    } else {
-                        interest = true;
-                    }
-                } else if (nRead == -1) {
-                    throw new EOFException();
-                }
-
-                if (log.isDebugEnabled()) {
-                    log.debug("Socket: [" + this + "], Read: [" + (len - leftToWrite) + "]");
-                }
-
-                return len - leftToWrite;
-            }
-        }
-
-
-        @Override
-        public void unRead(ByteBuffer returnedInput) {
-            if (returnedInput != null) {
-                getSocket().getBufHandler().getReadBuffer().put(returnedInput);
-            }
-        }
-
-
-        @Override
-        public void close() throws IOException {
-            getSocket().close();
-        }
-
-
-        private int fillReadBuffer(boolean block) throws IOException {
-            ByteBuffer readBuffer = getSocket().getBufHandler().getReadBuffer();
-            int nRead = 0;
-            if (block) {
-                readPending = true;
-                readBuffer.clear();
-                flipped = false;
-                try {
-                    nRead = getSocket().read(readBuffer)
-                            .get(getTimeout(), TimeUnit.MILLISECONDS).intValue();
-                    readPending = false;
-                } catch (ExecutionException e) {
-                    if (e.getCause() instanceof IOException) {
-                        throw (IOException) e.getCause();
-                    } else {
-                        throw new IOException(e);
-                    }
-                } catch (InterruptedException e) {
-                    throw new IOException(e);
-                } catch (TimeoutException e) {
-                    SocketTimeoutException ex = new SocketTimeoutException();
-                    throw ex;
-                }
-            } else {
-                readPending = true;
-                readBuffer.clear();
-                flipped = false;
-                Nio2Endpoint.startInline();
-                getSocket().read(readBuffer, getTimeout(), TimeUnit.MILLISECONDS,
-                        this, readCompletionHandler);
-                Nio2Endpoint.endInline();
-                if (!readPending) {
-                    nRead = readBuffer.position();
-                }
-            }
-            return nRead;
-        }
-
-
-        @Override
-        public int write(boolean block, byte[] b, int off, int len) throws IOException {
-            int leftToWrite = len;
-            int count = 0;
-            int offset = off;
-
-            while (leftToWrite > 0) {
-                int writeThisLoop;
-                int writtenThisLoop;
-
-                if (leftToWrite > maxWrite) {
-                    writeThisLoop = maxWrite;
-                } else {
-                    writeThisLoop = leftToWrite;
-                }
-
-                writtenThisLoop = writeInternal(block, b, offset, writeThisLoop);
-                if (writtenThisLoop < 0) {
-                    throw new EOFException();
-                }
-                count += writtenThisLoop;
-                if (!block && writePending.availablePermits() == 0) {
-                    // Prevent concurrent writes in non blocking mode,
-                    // leftover data has to be buffered
-                    return count;
-                }
-                offset += writtenThisLoop;
-                leftToWrite -= writtenThisLoop;
-
-                if (writtenThisLoop < writeThisLoop) {
-                    break;
-                }
-            }
-
-            return count;
-        }
-
-
-        private int writeInternal(boolean block, byte[] b, int off, int len)
-                throws IOException {
-            ByteBuffer writeBuffer = getSocket().getBufHandler().getWriteBuffer();
-            int written = 0;
-            if (block) {
-                writeBuffer.clear();
-                writeBuffer.put(b, off, len);
-                writeBuffer.flip();
-                try {
-                    written = getSocket().write(writeBuffer).get(getTimeout(), TimeUnit.MILLISECONDS).intValue();
-                } catch (ExecutionException e) {
-                    if (e.getCause() instanceof IOException) {
-                        throw (IOException) e.getCause();
-                    } else {
-                        throw new IOException(e);
-                    }
-                } catch (InterruptedException e) {
-                    throw new IOException(e);
-                } catch (TimeoutException e) {
-                    SocketTimeoutException ex = new SocketTimeoutException();
-                    throw ex;
-                }
-            } else {
-                if (writePending.tryAcquire()) {
-                    writeBuffer.clear();
-                    writeBuffer.put(b, off, len);
-                    writeBuffer.flip();
-                    Nio2Endpoint.startInline();
-                    getSocket().write(writeBuffer, getTimeout(), TimeUnit.MILLISECONDS, writeBuffer, writeCompletionHandler);
-                    Nio2Endpoint.endInline();
-                    written = len;
-                }
-            }
-            return written;
-        }
-
-
-        @Override
-        public void flush() throws IOException {
-            try {
-                // Block until a possible non blocking write is done
-                if (writePending.tryAcquire(getTimeout(), TimeUnit.MILLISECONDS)) {
-                    writePending.release();
-                    getSocket().flush().get(getTimeout(), TimeUnit.MILLISECONDS);
-                } else {
-                    throw new TimeoutException();
-                }
-            } catch (ExecutionException e) {
-                if (e.getCause() instanceof IOException) {
-                    throw (IOException) e.getCause();
-                } else {
-                    throw new IOException(e);
-                }
-            } catch (InterruptedException e) {
-                throw new IOException(e);
-            } catch (TimeoutException e) {
-                SocketTimeoutException ex = new SocketTimeoutException();
-                throw ex;
-            }
-        }
-
-        @Override
-        public void regsiterForEvent(boolean read, boolean write) {
-            // NO-OP. Appropriate handlers will already have been registered.
-        }
     }
 
-
     // ------------------------------------------------ Application Buffer Handler
     public static class NioBufferHandler implements ApplicationBufferHandler {
         private ByteBuffer readbuf = null;
@@ -1127,8 +821,10 @@
      * stored in the ThreadWithAttributes extra folders, or alternately in
      * thread local fields.
      */
-    public interface Handler extends AbstractEndpoint.Handler<Nio2Channel> {
-        public void release(SocketWrapperBase<Nio2Channel> socket);
+    public interface Handler extends AbstractEndpoint.Handler {
+        public SocketState process(SocketWrapper<Nio2Channel> socket,
+                SocketStatus status);
+        public void release(SocketWrapper<Nio2Channel> socket);
         public void closeAll();
         public SSLImplementation getSslImplementation();
         public void onCreateSSLEngine(SSLEngine engine);
@@ -1137,11 +833,11 @@
     /**
      * The completion handler used for asynchronous read operations
      */
-    private CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> awaitBytes
-            = new CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>>() {
+    private CompletionHandler<Integer, SocketWrapper<Nio2Channel>> awaitBytes
+            = new CompletionHandler<Integer, SocketWrapper<Nio2Channel>>() {
 
         @Override
-        public synchronized void completed(Integer nBytes, SocketWrapperBase<Nio2Channel> attachment) {
+        public synchronized void completed(Integer nBytes, SocketWrapper<Nio2Channel> attachment) {
             if (nBytes.intValue() < 0) {
                 failed(new ClosedChannelException(), attachment);
                 return;
@@ -1150,16 +846,16 @@
         }
 
         @Override
-        public void failed(Throwable exc, SocketWrapperBase<Nio2Channel> attachment) {
+        public void failed(Throwable exc, SocketWrapper<Nio2Channel> attachment) {
             processSocket0(attachment, SocketStatus.DISCONNECT, true);
         }
     };
 
-    public void addTimeout(SocketWrapperBase<Nio2Channel> socket) {
+    public void addTimeout(SocketWrapper<Nio2Channel> socket) {
         waitingRequests.add(socket);
     }
 
-    public boolean removeTimeout(SocketWrapperBase<Nio2Channel> socket) {
+    public boolean removeTimeout(SocketWrapper<Nio2Channel> socket) {
         return waitingRequests.remove(socket);
     }
 
@@ -1180,7 +876,7 @@
         }
     }
 
-    public void awaitBytes(SocketWrapperBase<Nio2Channel> socket) {
+    public void awaitBytes(SocketWrapper<Nio2Channel> socket) {
         if (socket == null || socket.getSocket() == null) {
             return;
         }
@@ -1323,14 +1019,14 @@
      */
     protected class SocketProcessor implements Runnable {
 
-        private SocketWrapperBase<Nio2Channel> socket = null;
+        private SocketWrapper<Nio2Channel> socket = null;
         private SocketStatus status = null;
 
-        public SocketProcessor(SocketWrapperBase<Nio2Channel> socket, SocketStatus status) {
+        public SocketProcessor(SocketWrapper<Nio2Channel> socket, SocketStatus status) {
             reset(socket,status);
         }
 
-        public void reset(SocketWrapperBase<Nio2Channel> socket, SocketStatus status) {
+        public void reset(SocketWrapper<Nio2Channel> socket, SocketStatus status) {
             this.socket = socket;
             this.status = status;
         }
@@ -1392,17 +1088,19 @@
                     }
                     if (state == SocketState.CLOSED) {
                         // Close socket and pool
-                        closeSocket(socket);
+                        socket.setComet(false);
+                        closeSocket(socket, SocketStatus.ERROR);
                         if (useCaches && running && !paused) {
                             nioChannels.push(socket.getSocket());
                             socketWrapperCache.push((Nio2SocketWrapper) socket);
                         }
                     } else if (state == SocketState.UPGRADING) {
                         socket.setKeptAlive(true);
+                        socket.access();
                         launch = true;
                     }
                 } else if (handshake == -1 ) {
-                    closeSocket(socket);
+                    closeSocket(socket, SocketStatus.DISCONNECT);
                     if (useCaches && running && !paused) {
                         nioChannels.push(socket.getSocket());
                         socketWrapperCache.push(((Nio2SocketWrapper) socket));
@@ -1412,7 +1110,7 @@
                 try {
                     oomParachuteData = null;
                     log.error("", oom);
-                    closeSocket(socket);
+                    closeSocket(socket, SocketStatus.ERROR);
                     releaseCaches();
                 } catch (Throwable oomt) {
                     try {
@@ -1427,7 +1125,7 @@
             } catch (Throwable t) {
                 log.error(sm.getString("endpoint.processing.fail"), t);
                 if (socket != null) {
-                    closeSocket(socket);
+                    closeSocket(socket, SocketStatus.ERROR);
                 }
             } finally {
                 if (launch) {
diff --git a/java/org/apache/tomcat/util/net/NioBlockingSelector.java b/java/org/apache/tomcat/util/net/NioBlockingSelector.java
index 3a5b04e..2f87e4c 100644
--- a/java/org/apache/tomcat/util/net/NioBlockingSelector.java
+++ b/java/org/apache/tomcat/util/net/NioBlockingSelector.java
@@ -35,7 +35,7 @@
 import org.apache.tomcat.util.ExceptionUtils;
 import org.apache.tomcat.util.collections.SynchronizedQueue;
 import org.apache.tomcat.util.collections.SynchronizedStack;
-import org.apache.tomcat.util.net.NioEndpoint.NioSocketWrapper;
+import org.apache.tomcat.util.net.NioEndpoint.KeyAttachment;
 
 public class NioBlockingSelector {
 
@@ -90,7 +90,7 @@
         if (reference == null) {
             reference = new KeyReference();
         }
-        NioSocketWrapper att = (NioSocketWrapper) key.attachment();
+        KeyAttachment att = (KeyAttachment) key.attachment();
         int written = 0;
         boolean timedout = false;
         int keycount = 1; //assume we can write
@@ -162,7 +162,7 @@
         if (reference == null) {
             reference = new KeyReference();
         }
-        NioSocketWrapper att = (NioSocketWrapper) key.attachment();
+        KeyAttachment att = (KeyAttachment) key.attachment();
         int read = 0;
         boolean timedout = false;
         int keycount = 1; //assume we can read
@@ -234,7 +234,7 @@
             if (wakeupCounter.addAndGet(1)==0) selector.wakeup();
         }
 
-        public void cancel(SelectionKey sk, NioSocketWrapper key, int ops){
+        public void cancel(SelectionKey sk, KeyAttachment key, int ops){
             if (sk!=null) {
                 sk.cancel();
                 sk.attach(null);
@@ -243,7 +243,7 @@
             }
         }
 
-        public void add(final NioSocketWrapper key, final int ops, final KeyReference ref) {
+        public void add(final KeyAttachment key, final int ops, final KeyReference ref) {
             if ( key == null ) return;
             NioChannel nch = key.getSocket();
             if ( nch == null ) return;
@@ -274,7 +274,7 @@
             wakeup();
         }
 
-        public void remove(final NioSocketWrapper key, final int ops) {
+        public void remove(final KeyAttachment key, final int ops) {
             if ( key == null ) return;
             NioChannel nch = key.getSocket();
             if ( nch == null ) return;
@@ -364,7 +364,7 @@
                     // any active event.
                     while (run && iterator != null && iterator.hasNext()) {
                         SelectionKey sk = iterator.next();
-                        NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
+                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
                         try {
                             attachment.access();
                             iterator.remove();
diff --git a/java/org/apache/tomcat/util/net/NioChannel.java b/java/org/apache/tomcat/util/net/NioChannel.java
index eaa8dd2..92b97b8 100644
--- a/java/org/apache/tomcat/util/net/NioChannel.java
+++ b/java/org/apache/tomcat/util/net/NioChannel.java
@@ -37,8 +37,8 @@
  */
 public class NioChannel implements ByteChannel {
 
-    protected static final StringManager sm = StringManager.getManager(
-            NioChannel.class.getPackage().getName());
+    protected static final StringManager sm =
+            StringManager.getManager("org.apache.tomcat.util.net.res");
 
     protected static ByteBuffer emptyBuf = ByteBuffer.allocate(0);
 
@@ -62,9 +62,6 @@
      */
     public void reset() throws IOException {
         bufHandler.getReadBuffer().clear();
-        // TODO AJP and HTTPS have different expectations for the state of
-        // the buffer at the start of a read. These need to be reconciled.
-        bufHandler.getReadBuffer().limit(0);
         bufHandler.getWriteBuffer().clear();
         this.sendFile = false;
     }
diff --git a/java/org/apache/tomcat/util/net/NioEndpoint.java b/java/org/apache/tomcat/util/net/NioEndpoint.java
index 8f2efd7..76245bb 100644
--- a/java/org/apache/tomcat/util/net/NioEndpoint.java
+++ b/java/org/apache/tomcat/util/net/NioEndpoint.java
@@ -17,7 +17,6 @@
 
 package org.apache.tomcat.util.net;
 
-import java.io.EOFException;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.IOException;
@@ -34,6 +33,7 @@
 import java.nio.channels.SocketChannel;
 import java.nio.channels.WritableByteChannel;
 import java.util.Iterator;
+import java.util.Set;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.concurrent.RejectedExecutionException;
@@ -93,6 +93,11 @@
     private ServerSocketChannel serverSock = null;
 
     /**
+     * use send file
+     */
+    private boolean useSendfile = true;
+
+    /**
      * The size of the OOM parachute.
      */
     private int oomParachute = 1024*1024;
@@ -127,7 +132,7 @@
     /**
      * Cache for key attachment objects
      */
-    private SynchronizedStack<NioSocketWrapper> keyCache;
+    private SynchronizedStack<KeyAttachment> keyCache;
 
     /**
      * Cache for poller events
@@ -179,6 +184,19 @@
 
 
     /**
+     * Allow comet request handling.
+     */
+    private boolean useComet = true;
+    public void setUseComet(boolean useComet) { this.useComet = useComet; }
+    @Override
+    public boolean getUseComet() { return useComet; }
+    @Override
+    public boolean getUseCometTimeout() { return getUseComet(); }
+    @Override
+    public boolean getUsePolling() { return true; } // Always supported
+
+
+    /**
      * Poller thread count.
      */
     private int pollerThreadCount = Math.min(2,Runtime.getRuntime().availableProcessors());
@@ -210,6 +228,10 @@
         this.socketProperties = socketProperties;
     }
 
+    public void setUseSendfile(boolean useSendfile) {
+        this.useSendfile = useSendfile;
+    }
+
     /**
      * Is deferAccept supported?
      */
@@ -480,6 +502,11 @@
         return selectorPool;
     }
 
+    @Override
+    public boolean getUseSendfile() {
+        return useSendfile;
+    }
+
     public int getOomParachute() {
         return oomParachute;
     }
@@ -576,22 +603,22 @@
 
 
     @Override
-    public void processSocket(SocketWrapperBase<NioChannel> socketWrapper,
+    public void processSocket(SocketWrapper<NioChannel> socketWrapper,
             SocketStatus socketStatus, boolean dispatch) {
         NioChannel socket = socketWrapper.getSocket();
         if (socket.isOpen() && dispatch && socketStatus == SocketStatus.OPEN_READ) {
             socket.getPoller().add(socket, OP_CALLBACK);
         } else {
-            processSocket((NioSocketWrapper) socketWrapper, socketStatus, dispatch);
+            processSocket((KeyAttachment) socketWrapper, socketStatus, dispatch);
         }
     }
 
-    protected boolean processSocket(NioSocketWrapper attachment, SocketStatus status, boolean dispatch) {
+    protected boolean processSocket(KeyAttachment attachment, SocketStatus status, boolean dispatch) {
         try {
             if (attachment == null) {
                 return false;
             }
-            attachment.setCallBackNotify(false); //will get reset upon next reg
+            attachment.setCometNotify(false); //will get reset upon next reg
             SocketProcessor sc = processorCache.pop();
             if ( sc == null ) sc = new SocketProcessor(attachment, status);
             else sc.reset(attachment, status);
@@ -742,13 +769,13 @@
 
         private NioChannel socket;
         private int interestOps;
-        private NioSocketWrapper key;
+        private KeyAttachment key;
 
-        public PollerEvent(NioChannel ch, NioSocketWrapper k, int intOps) {
+        public PollerEvent(NioChannel ch, KeyAttachment k, int intOps) {
             reset(ch, k, intOps);
         }
 
-        public void reset(NioChannel ch, NioSocketWrapper k, int intOps) {
+        public void reset(NioChannel ch, KeyAttachment k, int intOps) {
             socket = ch;
             interestOps = intOps;
             key = k;
@@ -771,20 +798,20 @@
                 try {
                     boolean cancel = false;
                     if (key != null) {
-                        final NioSocketWrapper att = (NioSocketWrapper) key.attachment();
+                        final KeyAttachment att = (KeyAttachment) key.attachment();
                         if ( att!=null ) {
                             //handle callback flag
                             if ((interestOps & OP_CALLBACK) == OP_CALLBACK ) {
-                                att.setCallBackNotify(true);
+                                att.setCometNotify(true);
                             } else {
-                                att.setCallBackNotify(false);
+                                att.setCometNotify(false);
                             }
                             interestOps = (interestOps & (~OP_CALLBACK));//remove the callback flag
                             att.access();//to prevent timeout
                             //we are registering the key to start with, reset the fairness counter.
                             int ops = key.interestOps() | interestOps;
                             att.interestOps(ops);
-                            if (att.getCallBackNotify()) key.interestOps(0);
+                            if (att.getCometNotify()) key.interestOps(0);
                             else key.interestOps(ops);
                         } else {
                             cancel = true;
@@ -792,10 +819,10 @@
                     } else {
                         cancel = true;
                     }
-                    if ( cancel ) socket.getPoller().cancelledKey(key);
+                    if ( cancel ) socket.getPoller().cancelledKey(key,SocketStatus.ERROR);
                 }catch (CancelledKeyException ckx) {
                     try {
-                        socket.getPoller().cancelledKey(key);
+                        socket.getPoller().cancelledKey(key,SocketStatus.DISCONNECT);
                     }catch (Exception ignore) {}
                 }
             }//end if
@@ -824,7 +851,12 @@
         private volatile int keyCount = 0;
 
         public Poller() throws IOException {
-            this.selector = Selector.open();
+            synchronized (Selector.class) {
+                // Selector.open() isn't thread safe
+                // http://bugs.sun.com/view_bug.do?bug_id=6427854
+                // Affects 1.6.0_29, fixed in 1.7.0_01
+                this.selector = Selector.open();
+            }
         }
 
         public int getKeyCount() { return keyCount; }
@@ -868,7 +900,7 @@
             }
             addEvent(r);
             if (close) {
-                NioEndpoint.NioSocketWrapper ka = (NioEndpoint.NioSocketWrapper)socket.getAttachment();
+                NioEndpoint.KeyAttachment ka = (NioEndpoint.KeyAttachment)socket.getAttachment();
                 processSocket(ka, SocketStatus.STOP, false);
             }
         }
@@ -906,8 +938,8 @@
          */
         public void register(final NioChannel socket) {
             socket.setPoller(this);
-            NioSocketWrapper key = keyCache.pop();
-            final NioSocketWrapper ka = key!=null?key:new NioSocketWrapper(socket, NioEndpoint.this);
+            KeyAttachment key = keyCache.pop();
+            final KeyAttachment ka = key!=null?key:new KeyAttachment(socket);
             ka.reset(this,socket,getSocketProperties().getSoTimeout());
             ka.setKeepAliveLeft(NioEndpoint.this.getMaxKeepAliveRequests());
             ka.setSecure(isSSLEnabled());
@@ -918,11 +950,23 @@
             addEvent(r);
         }
 
-        public NioSocketWrapper cancelledKey(SelectionKey key) {
-            NioSocketWrapper ka = null;
+        public KeyAttachment cancelledKey(SelectionKey key, SocketStatus status) {
+            KeyAttachment ka = null;
             try {
                 if ( key == null ) return null;//nothing to do
-                ka = (NioSocketWrapper) key.attach(null);
+                ka = (KeyAttachment) key.attachment();
+                if (ka != null && ka.isComet() && status != null) {
+                    ka.setComet(false);//to avoid a loop
+                    if (status == SocketStatus.TIMEOUT ) {
+                        if (processSocket(ka, status, true)) {
+                            return null; // don't close on comet timeout
+                        }
+                    } else {
+                        // Don't dispatch if the lines below are cancelling the key
+                        processSocket(ka, status, false);
+                    }
+                }
+                ka = (KeyAttachment) key.attach(null);
                 if (ka!=null) handler.release(ka);
                 else handler.release((SocketChannel)key.channel());
                 if (key.isValid()) key.cancel();
@@ -1034,7 +1078,7 @@
                     // any active event.
                     while (iterator != null && iterator.hasNext()) {
                         SelectionKey sk = iterator.next();
-                        NioSocketWrapper attachment = (NioSocketWrapper)sk.attachment();
+                        KeyAttachment attachment = (KeyAttachment)sk.attachment();
                         // Attachment may be null if another thread has called
                         // cancelledKey()
                         if (attachment == null) {
@@ -1068,11 +1112,11 @@
             stopLatch.countDown();
         }
 
-        protected boolean processKey(SelectionKey sk, NioSocketWrapper attachment) {
+        protected boolean processKey(SelectionKey sk, KeyAttachment attachment) {
             boolean result = true;
             try {
                 if ( close ) {
-                    cancelledKey(sk);
+                    cancelledKey(sk, SocketStatus.STOP);
                 } else if ( sk.isValid() && attachment != null ) {
                     attachment.access();//make sure we don't time out valid sockets
                     if (sk.isReadable() || sk.isWritable() ) {
@@ -1094,7 +1138,7 @@
                                     }
                                 }
                                 if (closeSocket) {
-                                    cancelledKey(sk);
+                                    cancelledKey(sk,SocketStatus.DISCONNECT);
                                 }
                             } else {
                                 result = false;
@@ -1103,10 +1147,10 @@
                     }
                 } else {
                     //invalid key
-                    cancelledKey(sk);
+                    cancelledKey(sk, SocketStatus.ERROR);
                 }
             } catch ( CancelledKeyException ckx ) {
-                cancelledKey(sk);
+                cancelledKey(sk, SocketStatus.ERROR);
             } catch (Throwable t) {
                 ExceptionUtils.handleThrowable(t);
                 log.error("",t);
@@ -1114,7 +1158,7 @@
             return result;
         }
 
-        public boolean processSendfile(SelectionKey sk, NioSocketWrapper attachment, boolean event) {
+        public boolean processSendfile(SelectionKey sk, KeyAttachment attachment, boolean event) {
             NioChannel sc = null;
             try {
                 unreg(sk, attachment, sk.readyOps());
@@ -1128,7 +1172,7 @@
                 if ( sd.fchannel == null ) {
                     File f = new File(sd.fileName);
                     if ( !f.exists() ) {
-                        cancelledKey(sk);
+                        cancelledKey(sk,SocketStatus.ERROR);
                         return false;
                     }
                     @SuppressWarnings("resource") // Closed when channel is closed
@@ -1184,7 +1228,7 @@
                         if (log.isDebugEnabled()) {
                             log.debug("Send file connection is being closed");
                         }
-                        cancelledKey(sk);
+                        cancelledKey(sk,SocketStatus.STOP);
                         return false;
                     }
                 } else {
@@ -1199,11 +1243,11 @@
                 }
             }catch ( IOException x ) {
                 if ( log.isDebugEnabled() ) log.debug("Unable to complete sendfile request:", x);
-                cancelledKey(sk);
+                cancelledKey(sk,SocketStatus.ERROR);
                 return false;
             }catch ( Throwable t ) {
                 log.error("",t);
-                cancelledKey(sk);
+                cancelledKey(sk, SocketStatus.ERROR);
                 return false;
             }finally {
                 if (sc!=null) sc.setSendFile(false);
@@ -1211,12 +1255,12 @@
             return true;
         }
 
-        protected void unreg(SelectionKey sk, NioSocketWrapper attachment, int readyOps) {
+        protected void unreg(SelectionKey sk, KeyAttachment attachment, int readyOps) {
             //this is a must, so that we don't have multiple threads messing with the socket
             reg(sk,attachment,sk.interestOps()& (~readyOps));
         }
 
-        protected void reg(SelectionKey sk, NioSocketWrapper attachment, int intops) {
+        protected void reg(SelectionKey sk, KeyAttachment attachment, int intops) {
             sk.interestOps(intops);
             attachment.interestOps(intops);
         }
@@ -1234,17 +1278,19 @@
                 return;
             }
             //timeout
+            Set<SelectionKey> keys = selector.keys();
             int keycount = 0;
-            for (SelectionKey key : selector.keys()) {
+            for (Iterator<SelectionKey> iter = keys.iterator(); iter.hasNext();) {
+                SelectionKey key = iter.next();
                 keycount++;
                 try {
-                    NioSocketWrapper ka = (NioSocketWrapper) key.attachment();
+                    KeyAttachment ka = (KeyAttachment) key.attachment();
                     if ( ka == null ) {
-                        cancelledKey(key); //we don't support any keys without attachments
+                        cancelledKey(key, SocketStatus.ERROR); //we don't support any keys without attachments
                     } else if ( ka.getError() ) {
-                        cancelledKey(key);//TODO this is not yet being used
-                    } else if (ka.getCallBackNotify() ) {
-                        ka.setCallBackNotify(false);
+                        cancelledKey(key, SocketStatus.ERROR);//TODO this is not yet being used
+                    } else if (ka.getCometNotify() ) {
+                        ka.setCometNotify(false);
                         int ops = ka.interestOps() & ~OP_CALLBACK;
                         reg(key,ka,0);//avoid multiple calls, this gets re-registered after invocation
                         ka.interestOps(ops);
@@ -1262,15 +1308,19 @@
                         } else if (isTimedout) {
                             key.interestOps(0);
                             ka.interestOps(0); //avoid duplicate timeout calls
-                            cancelledKey(key);
+                            cancelledKey(key, SocketStatus.TIMEOUT);
                         }
-                    } else if (ka.isAsync()) {
+                    } else if (ka.isAsync() || ka.isComet()) {
                         if (close) {
                             key.interestOps(0);
                             ka.interestOps(0); //avoid duplicate stop calls
                             processKey(key,ka);
-                        } else if (ka.getAsyncTimeout() > 0) {
-                            if ((now - ka.getLastAsyncStart()) > ka.getAsyncTimeout()) {
+                        } else if (!ka.isAsync() || ka.getTimeout() > 0) {
+                            // Async requests with a timeout of 0 or less never timeout
+                            long delta = now - ka.getLastAccess();
+                            long timeout = (ka.getTimeout()==-1)?((long) socketProperties.getSoTimeout()):(ka.getTimeout());
+                            boolean isTimedout = delta > timeout;
+                            if (isTimedout) {
                                 // Prevent subsequent timeouts if the timeout event takes a while to process
                                 ka.access(Long.MAX_VALUE);
                                 processSocket(ka, SocketStatus.TIMEOUT, true);
@@ -1278,7 +1328,7 @@
                         }
                     }//end if
                 }catch ( CancelledKeyException ckx ) {
-                    cancelledKey(key);
+                    cancelledKey(key, SocketStatus.ERROR);
                 }
             }//for
             long prevExp = nextExpiration; //for logging purposes only
@@ -1294,30 +1344,17 @@
         }
     }
 
-    // ---------------------------------------------------- Key Attachment Class
-    public static class NioSocketWrapper extends SocketWrapperBase<NioChannel> {
+// ----------------------------------------------------- Key Attachment Class
+    public static class KeyAttachment extends SocketWrapper<NioChannel> {
 
-        private final int maxWrite;
-        private final NioSelectorPool pool;
-
-        private Poller poller = null;
-        private int interestOps = 0;
-        private boolean callBackNotify = false;
-        private CountDownLatch readLatch = null;
-        private CountDownLatch writeLatch = null;
-        private volatile SendfileData sendfileData = null;
-        private long writeTimeout = -1;
-
-        public NioSocketWrapper(NioChannel channel, NioEndpoint endpoint) {
-            super(channel, endpoint);
-            maxWrite = channel.getBufHandler().getWriteBuffer().capacity();
-            pool = endpoint.getSelectorPool();
+        public KeyAttachment(NioChannel channel) {
+            super(channel);
         }
 
         public void reset(Poller poller, NioChannel channel, long soTimeout) {
             super.reset(channel, soTimeout);
 
-            callBackNotify = false;
+            cometNotify = false;
             interestOps = 0;
             this.poller = poller;
             sendfileData = null;
@@ -1349,8 +1386,8 @@
 
         public Poller getPoller() { return poller;}
         public void setPoller(Poller poller){this.poller = poller;}
-        public void setCallBackNotify(boolean notify) { this.callBackNotify = notify; }
-        public boolean getCallBackNotify() { return callBackNotify; }
+        public void setCometNotify(boolean notify) { this.cometNotify = notify; }
+        public boolean getCometNotify() { return cometNotify; }
         public int interestOps() { return interestOps;}
         public int interestOps(int ops) { this.interestOps  = ops; return ops; }
         public CountDownLatch getReadLatch() { return readLatch; }
@@ -1389,251 +1426,29 @@
         }
         public long getWriteTimeout() {return this.writeTimeout;}
 
+        private Poller poller = null;
+        private int interestOps = 0;
+        private boolean cometNotify = false;
+        private CountDownLatch readLatch = null;
+        private CountDownLatch writeLatch = null;
+        private volatile SendfileData sendfileData = null;
+        private long writeTimeout = -1;
 
-        @Override
-        public boolean isReady() throws IOException {
-            ByteBuffer readBuffer = getSocket().getBufHandler().getReadBuffer();
-
-            if (readBuffer.remaining() > 0) {
-                return true;
-            }
-
-            readBuffer.clear();
-            fillReadBuffer(false);
-
-            boolean isReady = readBuffer.position() > 0;
-            readBuffer.flip();
-            return isReady;
-        }
-
-
-        @Override
-        public int read(boolean block, byte[] b, int off, int len)
-                throws IOException {
-
-            ByteBuffer readBuffer = getSocket().getBufHandler().getReadBuffer();
-            int remaining = readBuffer.remaining();
-
-            // Is there enough data in the read buffer to satisfy this request?
-            if (remaining >= len) {
-                readBuffer.get(b, off, len);
-                return len;
-            }
-
-            // Copy what data there is in the read buffer to the byte array
-            int leftToWrite = len;
-            int newOffset = off;
-            if (remaining > 0) {
-                readBuffer.get(b, off, remaining);
-                leftToWrite -= remaining;
-                newOffset += remaining;
-            }
-
-            // Fill the read buffer as best we can
-            readBuffer.clear();
-            int nRead = fillReadBuffer(block);
-
-            // Full as much of the remaining byte array as possible with the data
-            // that was just read
-            if (nRead > 0) {
-                readBuffer.flip();
-                if (nRead > leftToWrite) {
-                    readBuffer.get(b, newOffset, leftToWrite);
-                    leftToWrite = 0;
-                } else {
-                    readBuffer.get(b, newOffset, nRead);
-                    leftToWrite -= nRead;
-                }
-            } else if (nRead == 0) {
-                readBuffer.flip();
-            } else if (nRead == -1) {
-                // TODO i18n
-                throw new EOFException();
-            }
-
-            return len - leftToWrite;
-        }
-
-
-        @Override
-        public void unRead(ByteBuffer returnedInput) {
-            if (returnedInput != null) {
-                ByteBuffer readBuffer = getSocket().getBufHandler().getReadBuffer();
-                if (readBuffer.remaining() > 0) {
-                    readBuffer.flip();
-                } else {
-                    readBuffer.clear();
-                }
-                readBuffer.put(returnedInput);
-                readBuffer.flip();
-            }
-        }
-
-
-        @Override
-        public void close() throws IOException {
-            getSocket().close();
-        }
-
-
-        private int fillReadBuffer(boolean block) throws IOException {
-            int nRead;
-            NioChannel channel = getSocket();
-            if (block) {
-                Selector selector = null;
-                try {
-                    selector = pool.get();
-                } catch ( IOException x ) {
-                    // Ignore
-                }
-                try {
-                    NioEndpoint.NioSocketWrapper att =
-                            (NioEndpoint.NioSocketWrapper) channel.getAttachment();
-                    if (att == null) {
-                        throw new IOException("Key must be cancelled.");
-                    }
-                    nRead = pool.read(channel.getBufHandler().getReadBuffer(),
-                            channel, selector, att.getTimeout());
-                } catch (EOFException eof) {
-                    nRead = -1;
-                } finally {
-                    if (selector != null) {
-                        pool.put(selector);
-                    }
-                }
-            } else {
-                nRead = channel.read(channel.getBufHandler().getReadBuffer());
-            }
-            return nRead;
-        }
-
-
-        @Override
-        public int write(boolean block, byte[] b, int off, int len) throws IOException {
-            int leftToWrite = len;
-            int count = 0;
-            int offset = off;
-
-            while (leftToWrite > 0) {
-                int writeThisLoop;
-                int writtenThisLoop;
-
-                if (leftToWrite > maxWrite) {
-                    writeThisLoop = maxWrite;
-                } else {
-                    writeThisLoop = leftToWrite;
-                }
-
-                writtenThisLoop = writeInternal(block, b, offset, writeThisLoop);
-                count += writtenThisLoop;
-                offset += writtenThisLoop;
-                leftToWrite -= writtenThisLoop;
-
-                if (writtenThisLoop < writeThisLoop) {
-                    break;
-                }
-            }
-
-            return count;
-        }
-
-
-        private int writeInternal (boolean block, byte[] b, int off, int len)
-                throws IOException {
-
-            NioEndpoint.NioSocketWrapper att =
-                    (NioEndpoint.NioSocketWrapper) getSocket().getAttachment();
-            if (att == null) {
-                throw new IOException("Key must be cancelled");
-            }
-
-            ByteBuffer writeBuffer = getSocket().getBufHandler().getWriteBuffer();
-            writeBuffer.clear();
-            writeBuffer.put(b, off, len);
-            writeBuffer.flip();
-
-            int written = 0;
-            long writeTimeout = att.getWriteTimeout();
-            Selector selector = null;
-            try {
-                selector = pool.get();
-            } catch ( IOException x ) {
-                //ignore
-            }
-            try {
-                written = pool.write(writeBuffer, getSocket(), selector,
-                        writeTimeout, block);
-            } finally {
-                if (selector != null) {
-                    pool.put(selector);
-                }
-            }
-            if (written < len) {
-                getSocket().getPoller().add(getSocket(), SelectionKey.OP_WRITE);
-            }
-            return written;
-        }
-
-
-        @Override
-        public void flush() throws IOException {
-            NioEndpoint.NioSocketWrapper att =
-                    (NioEndpoint.NioSocketWrapper) getSocket().getAttachment();
-            if (att == null) {
-                throw new IOException("Key must be cancelled");
-            }
-            long writeTimeout = att.getWriteTimeout();
-            Selector selector = null;
-            try {
-                selector = pool.get();
-            } catch ( IOException x ) {
-                //ignore
-            }
-            try {
-                do {
-                    if (getSocket().flush(true, selector, writeTimeout)) {
-                        break;
-                    }
-                } while (true);
-            } finally {
-                if (selector != null) {
-                    pool.put(selector);
-                }
-            }
-        }
-
-        @Override
-        public void regsiterForEvent(boolean read, boolean write) {
-            SelectionKey key = getSocket().getIOChannel().keyFor(
-                    getSocket().getPoller().getSelector());
-            if (read) {
-                this.interestOps(this.interestOps() | SelectionKey.OP_READ);
-                key.interestOps(key.interestOps() | SelectionKey.OP_READ);
-            }
-            if (write) {
-                this.interestOps(this.interestOps() | SelectionKey.OP_WRITE);
-                key.interestOps(key.interestOps() | SelectionKey.OP_READ);
-            }
-        }
     }
 
-
     // ------------------------------------------------ Application Buffer Handler
     public static class NioBufferHandler implements ApplicationBufferHandler {
         private ByteBuffer readbuf = null;
         private ByteBuffer writebuf = null;
 
         public NioBufferHandler(int readsize, int writesize, boolean direct) {
-            if (direct) {
+            if ( direct ) {
                 readbuf = ByteBuffer.allocateDirect(readsize);
                 writebuf = ByteBuffer.allocateDirect(writesize);
-            } else {
+            }else {
                 readbuf = ByteBuffer.allocate(readsize);
                 writebuf = ByteBuffer.allocate(writesize);
             }
-            // TODO AJP and HTTPS have different expectations for the state of
-            // the buffer at the start of a read. These need to be reconciled.
-            readbuf.limit(0);
         }
 
         @Override
@@ -1653,8 +1468,10 @@
      * stored in the ThreadWithAttributes extra folders, or alternately in
      * thread local fields.
      */
-    public interface Handler extends AbstractEndpoint.Handler<NioChannel> {
-        public void release(SocketWrapperBase<NioChannel> socket);
+    public interface Handler extends AbstractEndpoint.Handler {
+        public SocketState process(SocketWrapper<NioChannel> socket,
+                SocketStatus status);
+        public void release(SocketWrapper<NioChannel> socket);
         public void release(SocketChannel socket);
         public SSLImplementation getSslImplementation();
         public void onCreateSSLEngine(SSLEngine engine);
@@ -1668,14 +1485,14 @@
      */
     protected class SocketProcessor implements Runnable {
 
-        private NioSocketWrapper ka = null;
+        private KeyAttachment ka = null;
         private SocketStatus status = null;
 
-        public SocketProcessor(NioSocketWrapper ka, SocketStatus status) {
+        public SocketProcessor(KeyAttachment ka, SocketStatus status) {
             reset(ka, status);
         }
 
-        public void reset(NioSocketWrapper ka, SocketStatus status) {
+        public void reset(KeyAttachment ka, SocketStatus status) {
             this.ka = ka;
             this.status = status;
         }
@@ -1700,7 +1517,7 @@
             }
         }
 
-        private void doRun(SelectionKey key, NioSocketWrapper ka) {
+        private void doRun(SelectionKey key, KeyAttachment ka) {
             NioChannel socket = ka.getSocket();
 
             try {
@@ -1743,7 +1560,8 @@
                     if (state == SocketState.CLOSED) {
                         // Close socket and pool
                         try {
-                            if (socket.getPoller().cancelledKey(key) != null) {
+                            ka.setComet(false);
+                            if (socket.getPoller().cancelledKey(key, SocketStatus.ERROR) != null) {
                                 // SocketWrapper (attachment) was removed from the
                                 // key - recycle both. This can only happen once
                                 // per attempted closure so it is used to determine
@@ -1768,7 +1586,7 @@
                     }
                 } else if (handshake == -1 ) {
                     if (key != null) {
-                        socket.getPoller().cancelledKey(key);
+                        socket.getPoller().cancelledKey(key, SocketStatus.DISCONNECT);
                     }
                     if (running && !paused) {
                         nioChannels.push(socket);
@@ -1783,14 +1601,14 @@
                 }
             } catch (CancelledKeyException cx) {
                 if (socket != null) {
-                    socket.getPoller().cancelledKey(key);
+                    socket.getPoller().cancelledKey(key, null);
                 }
             } catch (OutOfMemoryError oom) {
                 try {
                     oomParachuteData = null;
                     log.error("", oom);
                     if (socket != null) {
-                        socket.getPoller().cancelledKey(key);
+                        socket.getPoller().cancelledKey(key,SocketStatus.ERROR);
                     }
                     releaseCaches();
                 } catch (Throwable oomt) {
@@ -1806,7 +1624,7 @@
             } catch (Throwable t) {
                 log.error("", t);
                 if (socket != null) {
-                    socket.getPoller().cancelledKey(key);
+                    socket.getPoller().cancelledKey(key,SocketStatus.ERROR);
                 }
             } finally {
                 socket = null;
diff --git a/java/org/apache/tomcat/util/net/NioSelectorPool.java b/java/org/apache/tomcat/util/net/NioSelectorPool.java
index 726f641..6cad111 100644
--- a/java/org/apache/tomcat/util/net/NioSelectorPool.java
+++ b/java/org/apache/tomcat/util/net/NioSelectorPool.java
@@ -63,7 +63,12 @@
         if (SHARED && SHARED_SELECTOR == null) {
             synchronized ( NioSelectorPool.class ) {
                 if ( SHARED_SELECTOR == null )  {
-                    SHARED_SELECTOR = Selector.open();
+                    synchronized (Selector.class) {
+                        // Selector.open() isn't thread safe
+                        // http://bugs.sun.com/view_bug.do?bug_id=6427854
+                        // Affects 1.6.0_29, fixed in 1.7.0_01
+                        SHARED_SELECTOR = Selector.open();
+                    }
                     log.info("Using a shared selector for servlet write/read");
                 }
             }
@@ -84,13 +89,23 @@
         try {
             s = selectors.size()>0?selectors.poll():null;
             if (s == null) {
-                s = Selector.open();
+                synchronized (Selector.class) {
+                    // Selector.open() isn't thread safe
+                    // http://bugs.sun.com/view_bug.do?bug_id=6427854
+                    // Affects 1.6.0_29, fixed in 1.7.0_01
+                    s = Selector.open();
+                }
             }
             else spare.decrementAndGet();
 
         }catch (NoSuchElementException x ) {
             try {
-                s = Selector.open();
+                synchronized (Selector.class) {
+                    // Selector.open() isn't thread safe
+                    // http://bugs.sun.com/view_bug.do?bug_id=6427854
+                    // Affects 1.6.0_29, fixed in 1.7.0_01
+                    s = Selector.open();
+                }
             } catch (IOException iox) {
             }
         } finally {
diff --git a/java/org/apache/tomcat/util/net/SSLImplementation.java b/java/org/apache/tomcat/util/net/SSLImplementation.java
index a0b7041..fd9f976 100644
--- a/java/org/apache/tomcat/util/net/SSLImplementation.java
+++ b/java/org/apache/tomcat/util/net/SSLImplementation.java
@@ -17,10 +17,9 @@
 
 package org.apache.tomcat.util.net;
 
-import javax.net.ssl.SSLSession;
+import java.net.Socket;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
+import javax.net.ssl.SSLSession;
 
 /* SSLImplementation:
 
@@ -29,7 +28,8 @@
  @author EKR
  */
 public abstract class SSLImplementation {
-    private static final Log logger = LogFactory.getLog(SSLImplementation.class);
+    private static final org.apache.juli.logging.Log logger = org.apache.juli.logging.LogFactory
+            .getLog(SSLImplementation.class);
 
     // The default implementations in our search path
     private static final String JSSEImplementationClass =
@@ -80,6 +80,11 @@
 
     public abstract String getImplementationName();
 
+    public abstract ServerSocketFactory getServerSocketFactory(
+            AbstractEndpoint<?> endpoint);
+
+    public abstract SSLSupport getSSLSupport(Socket sock);
+
     public abstract SSLSupport getSSLSupport(SSLSession session);
 
     public abstract SSLUtil getSSLUtil(AbstractEndpoint<?> ep);
diff --git a/java/org/apache/tomcat/util/net/SecureNio2Channel.java b/java/org/apache/tomcat/util/net/SecureNio2Channel.java
index 8e623b6..ba16492 100644
--- a/java/org/apache/tomcat/util/net/SecureNio2Channel.java
+++ b/java/org/apache/tomcat/util/net/SecureNio2Channel.java
@@ -41,8 +41,7 @@
  */
 public class SecureNio2Channel extends Nio2Channel  {
 
-    protected static final StringManager sm = StringManager.getManager(
-            SecureNio2Channel.class.getPackage().getName());
+    protected static final StringManager sm = StringManager.getManager("org.apache.tomcat.util.net.res");
 
     protected ByteBuffer netInBuffer;
     protected ByteBuffer netOutBuffer;
@@ -58,8 +57,8 @@
     protected volatile boolean readPending;
     protected volatile boolean writePending;
 
-    private CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> handshakeReadCompletionHandler;
-    private CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>> handshakeWriteCompletionHandler;
+    private CompletionHandler<Integer, SocketWrapper<Nio2Channel>> handshakeReadCompletionHandler;
+    private CompletionHandler<Integer, SocketWrapper<Nio2Channel>> handshakeWriteCompletionHandler;
 
     public SecureNio2Channel(SSLEngine engine, ApplicationBufferHandler bufHandler,
             Nio2Endpoint endpoint0) {
@@ -74,9 +73,9 @@
             netInBuffer = ByteBuffer.allocate(netBufSize);
             netOutBuffer = ByteBuffer.allocate(netBufSize);
         }
-        handshakeReadCompletionHandler = new CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>>() {
+        handshakeReadCompletionHandler = new CompletionHandler<Integer, SocketWrapper<Nio2Channel>>() {
             @Override
-            public void completed(Integer result, SocketWrapperBase<Nio2Channel> attachment) {
+            public void completed(Integer result, SocketWrapper<Nio2Channel> attachment) {
                 if (result.intValue() < 0) {
                     failed(new EOFException(), attachment);
                     return;
@@ -84,13 +83,13 @@
                 endpoint.processSocket(attachment, SocketStatus.OPEN_READ, false);
             }
             @Override
-            public void failed(Throwable exc, SocketWrapperBase<Nio2Channel> attachment) {
-                endpoint.closeSocket(attachment);
+            public void failed(Throwable exc, SocketWrapper<Nio2Channel> attachment) {
+                endpoint.closeSocket(attachment, SocketStatus.ERROR);
             }
         };
-        handshakeWriteCompletionHandler = new CompletionHandler<Integer, SocketWrapperBase<Nio2Channel>>() {
+        handshakeWriteCompletionHandler = new CompletionHandler<Integer, SocketWrapper<Nio2Channel>>() {
             @Override
-            public void completed(Integer result, SocketWrapperBase<Nio2Channel> attachment) {
+            public void completed(Integer result, SocketWrapper<Nio2Channel> attachment) {
                 if (result.intValue() < 0) {
                     failed(new EOFException(), attachment);
                     return;
@@ -98,8 +97,8 @@
                 endpoint.processSocket(attachment, SocketStatus.OPEN_WRITE, false);
             }
             @Override
-            public void failed(Throwable exc, SocketWrapperBase<Nio2Channel> attachment) {
-                endpoint.closeSocket(attachment);
+            public void failed(Throwable exc, SocketWrapper<Nio2Channel> attachment) {
+                endpoint.closeSocket(attachment, SocketStatus.ERROR);
             }
         };
     }
@@ -109,7 +108,7 @@
     }
 
     @Override
-    public void reset(AsynchronousSocketChannel channel, SocketWrapperBase<Nio2Channel> socket)
+    public void reset(AsynchronousSocketChannel channel, SocketWrapper<Nio2Channel> socket)
             throws IOException {
         super.reset(channel, socket);
         netOutBuffer.position(0);
diff --git a/java/org/apache/tomcat/util/net/SecureNioChannel.java b/java/org/apache/tomcat/util/net/SecureNioChannel.java
index 87f7426..90543b1 100644
--- a/java/org/apache/tomcat/util/net/SecureNioChannel.java
+++ b/java/org/apache/tomcat/util/net/SecureNioChannel.java
@@ -196,10 +196,6 @@
                     } else if ( handshake.getStatus() == Status.BUFFER_UNDERFLOW ){
                         //read more data, reregister for OP_READ
                         return SelectionKey.OP_READ;
-                    } else if (handshake.getStatus() == Status.BUFFER_OVERFLOW) {
-                        // TODO AJP and HTTPS have different expectations for the state of
-                        // the buffer at the start of a read. These need to be reconciled.
-                        bufHandler.getReadBuffer().compact();
                     } else {
                         throw new IOException(sm.getString("channel.nio.ssl.unexpectedStatusDuringWrap", handshakeStatus));
                     }//switch
@@ -247,7 +243,12 @@
                     default : {
                         long now = System.currentTimeMillis();
                         if (selector==null) {
-                            selector = Selector.open();
+                            synchronized (Selector.class) {
+                                // Selector.open() isn't thread safe
+                                // http://bugs.sun.com/view_bug.do?bug_id=6427854
+                                // Affects 1.6.0_29, fixed in 1.7.0_01
+                                selector = Selector.open();
+                            }
                             key = getIOChannel().register(selector, hsStatus);
                         } else {
                             key.interestOps(hsStatus); // null warning supressed
diff --git a/java/org/apache/tomcat/util/net/ServerSocketFactory.java b/java/org/apache/tomcat/util/net/ServerSocketFactory.java
new file mode 100644
index 0000000..1265bb1
--- /dev/null
+++ b/java/org/apache/tomcat/util/net/ServerSocketFactory.java
@@ -0,0 +1,97 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+
+/**
+ * The common interface through which the {@link JIoEndpoint} interacts with
+ * both non-SSL and SSL sockets.
+ */
+public interface ServerSocketFactory {
+
+    /**
+     * Returns a server socket which uses all network interfaces on the host,
+     * and is bound to a the specified port. The socket is configured with the
+     * socket options (such as accept timeout) given to this factory.
+     *
+     * @param port
+     *            the port to listen to
+     * @exception IOException
+     *                for networking errors
+     * @exception InstantiationException
+     *                for construction errors
+     */
+    ServerSocket createSocket(int port) throws IOException,
+            InstantiationException;
+
+    /**
+     * Returns a server socket which uses all network interfaces on the host, is
+     * bound to a the specified port, and uses the specified connection backlog.
+     * The socket is configured with the socket options (such as accept timeout)
+     * given to this factory.
+     *
+     * @param port
+     *            the port to listen to
+     * @param backlog
+     *            how many connections are queued
+     * @exception IOException
+     *                for networking errors
+     * @exception InstantiationException
+     *                for construction errors
+     */
+    ServerSocket createSocket(int port, int backlog) throws IOException,
+            InstantiationException;
+
+    /**
+     * Returns a server socket which uses only the specified network interface
+     * on the local host, is bound to a the specified port, and uses the
+     * specified connection backlog. The socket is configured with the socket
+     * options (such as accept timeout) given to this factory.
+     *
+     * @param port
+     *            the port to listen to
+     * @param backlog
+     *            how many connections are queued
+     * @param ifAddress
+     *            the network interface address to use
+     * @exception IOException
+     *                for networking errors
+     * @exception InstantiationException
+     *                for construction errors
+     */
+    ServerSocket createSocket(int port, int backlog, InetAddress ifAddress)
+            throws IOException, InstantiationException;
+
+    /**
+     * Wrapper function for accept(). This allows us to trap and translate
+     * exceptions if necessary.
+     *
+     * @exception IOException
+     */
+    Socket acceptSocket(ServerSocket socket) throws IOException;
+
+    /**
+     * Triggers the SSL handshake. This will be a no-op for non-SSL sockets.
+     *
+     * @exception IOException
+     */
+    void handshake(Socket sock) throws IOException;
+}
diff --git a/java/org/apache/tomcat/util/net/SocketWrapperBase.java b/java/org/apache/tomcat/util/net/SocketWrapper.java
similarity index 70%
rename from java/org/apache/tomcat/util/net/SocketWrapperBase.java
rename to java/org/apache/tomcat/util/net/SocketWrapper.java
index 7d48426..5cd540d 100644
--- a/java/org/apache/tomcat/util/net/SocketWrapperBase.java
+++ b/java/org/apache/tomcat/util/net/SocketWrapper.java
@@ -16,8 +16,6 @@
  */
 package org.apache.tomcat.util.net;
 
-import java.io.IOException;
-import java.nio.ByteBuffer;
 import java.util.Iterator;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArraySet;
@@ -25,17 +23,15 @@
 import java.util.concurrent.locks.ReentrantReadWriteLock;
 import java.util.concurrent.locks.ReentrantReadWriteLock.WriteLock;
 
-public abstract class SocketWrapperBase<E> {
+public class SocketWrapper<E> {
 
     private volatile E socket;
-    private final AbstractEndpoint<E> endpoint;
 
     private volatile long lastAccess = System.currentTimeMillis();
-    private volatile long lastAsyncStart = 0;
-    private volatile long asyncTimeout = -1;
     private long timeout = -1;
     private boolean error = false;
     private volatile int keepAliveLeft = 100;
+    private volatile boolean comet = false;
     private volatile boolean async = false;
     private boolean keptAlive = false;
     private volatile boolean upgraded = false;
@@ -69,9 +65,8 @@
 
     private Set<DispatchType> dispatches = new CopyOnWriteArraySet<>();
 
-    public SocketWrapperBase(E socket, AbstractEndpoint<E> endpoint) {
+    public SocketWrapper(E socket) {
         this.socket = socket;
-        this.endpoint = endpoint;
         ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
         this.blockingStatusReadLock = lock.readLock();
         this.blockingStatusWriteLock = lock.writeLock();
@@ -81,53 +76,25 @@
         return socket;
     }
 
-    public AbstractEndpoint<E> getEndpoint() {
-        return endpoint;
-    }
-
+    public boolean isComet() { return comet; }
+    public void setComet(boolean comet) { this.comet = comet; }
     public boolean isAsync() { return async; }
-    /**
-     * Sets the async flag for this connection. If this call causes the
-     * connection to transition from non-async to async then the lastAsyncStart
-     * property will be set using the current time. This property is used as the
-     * start time when calculating the async timeout. As per the Servlet spec
-     * the async timeout applies once the dispatch where startAsync() was called
-     * has returned to the container (which is when this method is currently
-     * called).
-     *
-     * @param async The new value of for the async flag
-     */
-    public void setAsync(boolean async) {
-        if (!this.async && async) {
-            lastAsyncStart = System.currentTimeMillis();
-        }
-        this.async = async;
-    }
-    /**
-     * Obtain the time that this connection last transitioned to async
-     * processing.
-     *
-     * @return The time (as returned by {@link System#currentTimeMillis()}) that
-     *         this connection last transitioned to async
-     */
-    public long getLastAsyncStart() {
-       return lastAsyncStart;
-    }
-    public void setAsyncTimeout(long timeout) {
-        asyncTimeout = timeout;
-    }
-    public long getAsyncTimeout() {
-        return asyncTimeout;
-    }
+    public void setAsync(boolean async) { this.async = async; }
     public boolean isUpgraded() { return upgraded; }
     public void setUpgraded(boolean upgraded) { this.upgraded = upgraded; }
     public boolean isSecure() { return secure; }
     public void setSecure(boolean secure) { this.secure = secure; }
     public long getLastAccess() { return lastAccess; }
     public void access() {
-        access(System.currentTimeMillis());
+        // Async timeouts are based on the time between the call to startAsync()
+        // and complete() / dispatch() so don't update the last access time
+        // (that drives the timeout) on every read and write when using async
+        // processing.
+        if (!isAsync()) {
+            access(System.currentTimeMillis());
+        }
     }
-    void access(long access) { lastAccess = access; }
+    public void access(long access) { lastAccess = access; }
     public void setTimeout(long timeout) {this.timeout = timeout;}
     public long getTimeout() {return this.timeout;}
     public boolean getError() { return error; }
@@ -188,12 +155,11 @@
     public void reset(E socket, long timeout) {
         async = false;
         blockingStatus = true;
+        comet = false;
         dispatches.clear();
         error = false;
         keepAliveLeft = 100;
         lastAccess = System.currentTimeMillis();
-        lastAsyncStart = 0;
-        asyncTimeout = -1;
         localAddr = null;
         localName = null;
         localPort = -1;
@@ -215,26 +181,4 @@
     public String toString() {
         return super.toString() + ":" + String.valueOf(socket);
     }
-
-
-    public abstract int read(boolean block, byte[] b, int off, int len) throws IOException;
-    public abstract boolean isReady() throws IOException;
-    /**
-     * Return input that has been read to the input buffer for re-reading by the
-     * correct component. There are times when a component may read more data
-     * than it needs before it passes control to another component. One example
-     * of this is during HTTP upgrade. If an (arguably misbehaving client) sends
-     * data associated with the upgraded protocol before the HTTP upgrade
-     * completes, the HTTP handler may read it. This method provides a way for
-     * that data to be returned so it can be processed by the correct component.
-     *
-     * @param input The input to return to the input buffer.
-     */
-    public abstract void unRead(ByteBuffer input);
-    public abstract void close() throws IOException;
-
-    public abstract int write(boolean block, byte[] b, int off, int len) throws IOException;
-    public abstract void flush() throws IOException;
-
-    public abstract void regsiterForEvent(boolean read, boolean write);
 }
diff --git a/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java b/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
index f784aca..4b269e9 100644
--- a/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
+++ b/java/org/apache/tomcat/util/net/jsse/JSSEImplementation.java
@@ -14,14 +14,19 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+
 package org.apache.tomcat.util.net.jsse;
 
+import java.net.Socket;
+
 import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLSocket;
 
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.SSLImplementation;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.net.SSLUtil;
+import org.apache.tomcat.util.net.ServerSocketFactory;
 
 /* JSSEImplementation:
 
@@ -38,6 +43,16 @@
     }
 
     @Override
+    public ServerSocketFactory getServerSocketFactory(AbstractEndpoint<?> endpoint)  {
+        return new JSSESocketFactory(endpoint);
+    }
+
+    @Override
+    public SSLSupport getSSLSupport(Socket s) {
+        return new JSSESupport((SSLSocket) s);
+    }
+
+    @Override
     public SSLSupport getSSLSupport(SSLSession session) {
         return new JSSESupport(session);
     }
diff --git a/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java b/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
index 16ac02f..283e784 100644
--- a/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
+++ b/java/org/apache/tomcat/util/net/jsse/JSSESocketFactory.java
@@ -22,7 +22,10 @@
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.InetAddress;
 import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketException;
 import java.security.KeyManagementException;
 import java.security.KeyStore;
 import java.security.NoSuchAlgorithmException;
@@ -51,16 +54,17 @@
 import javax.net.ssl.SSLException;
 import javax.net.ssl.SSLServerSocket;
 import javax.net.ssl.SSLServerSocketFactory;
+import javax.net.ssl.SSLSession;
 import javax.net.ssl.SSLSessionContext;
+import javax.net.ssl.SSLSocket;
 import javax.net.ssl.TrustManager;
 import javax.net.ssl.TrustManagerFactory;
 import javax.net.ssl.X509KeyManager;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.AbstractEndpoint;
 import org.apache.tomcat.util.net.Constants;
 import org.apache.tomcat.util.net.SSLUtil;
+import org.apache.tomcat.util.net.ServerSocketFactory;
 import org.apache.tomcat.util.net.jsse.openssl.OpenSSLCipherConfigurationParser;
 import org.apache.tomcat.util.res.StringManager;
 
@@ -76,9 +80,10 @@
  * @author EKR -- renamed to JSSESocketFactory
  * @author Jan Luehe
  */
-public class JSSESocketFactory implements SSLUtil {
+public class JSSESocketFactory implements ServerSocketFactory, SSLUtil {
 
-    private static final Log log = LogFactory.getLog(JSSESocketFactory.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(JSSESocketFactory.class);
     private static final StringManager sm =
         StringManager.getManager("org.apache.tomcat.util.net.jsse.res");
 
@@ -94,6 +99,7 @@
 
     private AbstractEndpoint<?> endpoint;
 
+    private final boolean rfc5746Supported;
     private final String[] defaultServerProtocols;
     private final String[] defaultServerCipherSuites;
 
@@ -131,9 +137,21 @@
             throw new IllegalArgumentException(e);
         }
 
+        // Supported cipher suites aren't accessible directly from the
+        // SSLContext so use the SSL server socket factory
+        SSLServerSocketFactory ssf = context.getServerSocketFactory();
+        String supportedCiphers[] = ssf.getSupportedCipherSuites();
+        boolean found = false;
+        for (String cipher : supportedCiphers) {
+            if ("TLS_EMPTY_RENEGOTIATION_INFO_SCSV".equals(cipher)) {
+                found = true;
+                break;
+            }
+        }
+        rfc5746Supported = found;
+
         // There is no standard way to determine the default protocols and
         // cipher suites so create a server socket to see what the defaults are
-        SSLServerSocketFactory ssf = context.getServerSocketFactory();
         SSLServerSocket socket;
         try {
             socket = (SSLServerSocket) ssf.createServerSocket();
@@ -171,6 +189,64 @@
 
 
     @Override
+    public ServerSocket createSocket (int port)
+        throws IOException
+    {
+        init();
+        ServerSocket socket = sslProxy.createServerSocket(port);
+        initServerSocket(socket);
+        return socket;
+    }
+
+    @Override
+    public ServerSocket createSocket (int port, int backlog)
+        throws IOException
+    {
+        init();
+        ServerSocket socket = sslProxy.createServerSocket(port, backlog);
+        initServerSocket(socket);
+        return socket;
+    }
+
+    @Override
+    public ServerSocket createSocket (int port, int backlog,
+                                      InetAddress ifAddress)
+        throws IOException
+    {
+        init();
+        ServerSocket socket = sslProxy.createServerSocket(port, backlog,
+                                                          ifAddress);
+        initServerSocket(socket);
+        return socket;
+    }
+
+    @Override
+    public Socket acceptSocket(ServerSocket socket)
+        throws IOException
+    {
+        SSLSocket asock = null;
+        try {
+             asock = (SSLSocket)socket.accept();
+        } catch (SSLException e){
+          throw new SocketException("SSL handshake error" + e.toString());
+        }
+        return asock;
+    }
+
+    @Override
+    public void handshake(Socket sock) throws IOException {
+        // We do getSession instead of startHandshake() so we can call this multiple times
+        SSLSession session = ((SSLSocket)sock).getSession();
+        if (session.getCipherSuite().equals("SSL_NULL_WITH_NULL_NULL"))
+            throw new IOException("SSL handshake failed. Ciper suite in SSL Session is SSL_NULL_WITH_NULL_NULL");
+
+        if (!allowUnsafeLegacyRenegotiation && !rfc5746Supported) {
+            // Prevent further handshakes by removing all cipher suites
+            ((SSLSocket) sock).setEnabledCipherSuites(new String[0]);
+        }
+    }
+
+    @Override
     public String[] getEnableableCiphers(SSLContext context) {
         String requestedCiphersStr = endpoint.getCiphers();
 
diff --git a/java/org/apache/tomcat/util/net/jsse/JSSESupport.java b/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
index 5131ad6..31d49ef 100644
--- a/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
+++ b/java/org/apache/tomcat/util/net/jsse/JSSESupport.java
@@ -33,8 +33,6 @@
 import javax.net.ssl.SSLSocket;
 import javax.security.cert.X509Certificate;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.net.SSLSessionManager;
 import org.apache.tomcat.util.net.SSLSupport;
 import org.apache.tomcat.util.res.StringManager;
@@ -55,7 +53,8 @@
 
 class JSSESupport implements SSLSupport, SSLSessionManager {
 
-    private static final Log log = LogFactory.getLog(JSSESupport.class);
+    private static final org.apache.juli.logging.Log log =
+        org.apache.juli.logging.LogFactory.getLog(JSSESupport.class);
 
     private static final StringManager sm =
         StringManager.getManager("org.apache.tomcat.util.net.jsse.res");
diff --git a/java/org/apache/tomcat/util/net/jsse/openssl/OpenSSLCipherConfigurationParser.java b/java/org/apache/tomcat/util/net/jsse/openssl/OpenSSLCipherConfigurationParser.java
index 6b65075..9ab5e8d 100644
--- a/java/org/apache/tomcat/util/net/jsse/openssl/OpenSSLCipherConfigurationParser.java
+++ b/java/org/apache/tomcat/util/net/jsse/openssl/OpenSSLCipherConfigurationParser.java
@@ -28,8 +28,6 @@
 import java.util.Map;
 import java.util.Set;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
 import org.apache.tomcat.util.res.StringManager;
 
 /**
@@ -37,7 +35,8 @@
  */
 public class OpenSSLCipherConfigurationParser {
 
-    private static final Log log = LogFactory.getLog(OpenSSLCipherConfigurationParser.class);
+    private static final org.apache.juli.logging.Log log =
+            org.apache.juli.logging.LogFactory.getLog(OpenSSLCipherConfigurationParser.class);
     private static final StringManager sm =
             StringManager.getManager("org.apache.tomcat.util.net.jsse.res");
 
diff --git a/java/org/apache/tomcat/util/net/LocalStrings.properties b/java/org/apache/tomcat/util/net/res/LocalStrings.properties
similarity index 91%
rename from java/org/apache/tomcat/util/net/LocalStrings.properties
rename to java/org/apache/tomcat/util/net/res/LocalStrings.properties
index 299b80c..765b69b 100644
--- a/java/org/apache/tomcat/util/net/LocalStrings.properties
+++ b/java/org/apache/tomcat/util/net/res/LocalStrings.properties
@@ -84,10 +84,3 @@
 channel.nio.ssl.incompleteHandshake=Handshake incomplete, you must complete handshake before reading data.
 channel.nio.ssl.closing=Channel is in closing state.
 channel.nio.ssl.invalidBuffer=You can only read using the application read buffer provided by the handler.
-
-socket.apr.clientAbort=The client aborted the connection.
-socket.apr.read.error=Unexpected error [{0}] reading data from the APR/native socket [{1}] with wrapper [{2}].
-socket.apr.read.sslGeneralError=An APR general error was returned by the SSL read operation on APR/native socket [{0}] with wrapper [{1}]. It will be treated as EAGAIN and the socket returned to the poller.
-socket.apr.write.error=Unexpected error [{0}] writing data to the APR/native socket [{1}] with wrapper [{2}].
-socket.apr.closed=The socket [{0}] associated with this connection has been closed.
-
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_es.properties b/java/org/apache/tomcat/util/net/res/LocalStrings_es.properties
similarity index 100%
rename from java/org/apache/tomcat/util/net/LocalStrings_es.properties
rename to java/org/apache/tomcat/util/net/res/LocalStrings_es.properties
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_fr.properties b/java/org/apache/tomcat/util/net/res/LocalStrings_fr.properties
similarity index 100%
rename from java/org/apache/tomcat/util/net/LocalStrings_fr.properties
rename to java/org/apache/tomcat/util/net/res/LocalStrings_fr.properties
diff --git a/java/org/apache/tomcat/util/net/LocalStrings_ja.properties b/java/org/apache/tomcat/util/net/res/LocalStrings_ja.properties
similarity index 100%
rename from java/org/apache/tomcat/util/net/LocalStrings_ja.properties
rename to java/org/apache/tomcat/util/net/res/LocalStrings_ja.properties
diff --git a/java/org/apache/tomcat/util/res/StringManager.java b/java/org/apache/tomcat/util/res/StringManager.java
index ae52aeb..034fdb5 100644
--- a/java/org/apache/tomcat/util/res/StringManager.java
+++ b/java/org/apache/tomcat/util/res/StringManager.java
@@ -14,6 +14,7 @@
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
+
 package org.apache.tomcat.util.res;
 
 import java.text.MessageFormat;
@@ -39,7 +40,7 @@
  * the package name given plus the suffix of "LocalStrings". In
  * practice, this means that the localized information will be contained
  * in a LocalStrings.properties file located in the package
- * directory of the class path.
+ * directory of the classpath.
  *
  * <p>Please see the documentation for java.util.ResourceBundle for
  * more information.
@@ -59,7 +60,6 @@
     private final ResourceBundle bundle;
     private final Locale locale;
 
-
     /**
      * Creates a new StringManager for a given package. This is a
      * private method and all access to it is arbitrated by the
@@ -73,15 +73,15 @@
         ResourceBundle bnd = null;
         try {
             bnd = ResourceBundle.getBundle(bundleName, locale);
-        } catch (MissingResourceException ex) {
+        } catch( MissingResourceException ex ) {
             // Try from the current loader (that's the case for trusted apps)
             // Should only be required if using a TC5 style classloader structure
             // where common != shared != server
             ClassLoader cl = Thread.currentThread().getContextClassLoader();
-            if (cl != null) {
+            if( cl != null ) {
                 try {
                     bnd = ResourceBundle.getBundle(bundleName, locale, cl);
-                } catch (MissingResourceException ex2) {
+                } catch(MissingResourceException ex2) {
                     // Ignore
                 }
             }
@@ -100,21 +100,19 @@
         }
     }
 
-
     /**
-     * Get a string from the underlying resource bundle or return null if the
-     * String is not found.
-     *
-     * @param key to desired resource String
-     *
-     * @return resource String matching <i>key</i> from underlying bundle or
-     *         null if not found.
-     *
-     * @throws IllegalArgumentException if <i>key</i> is null
+        Get a string from the underlying resource bundle or return
+        null if the String is not found.
+
+        @param key to desired resource String
+        @return resource String matching <i>key</i> from underlying
+                bundle or null if not found.
+        @throws IllegalArgumentException if <i>key</i> is null.
      */
     public String getString(String key) {
-        if (key == null){
+        if(key == null){
             String msg = "key may not have a null value";
+
             throw new IllegalArgumentException(msg);
         }
 
@@ -125,7 +123,7 @@
             if (bundle != null) {
                 str = bundle.getString(key);
             }
-        } catch (MissingResourceException mre) {
+        } catch(MissingResourceException mre) {
             //bad: shouldn't mask an exception the following way:
             //   str = "[cannot find message associated with key '" + key +
             //         "' due to " + mre + "]";
@@ -143,13 +141,12 @@
         return str;
     }
 
-
     /**
      * Get a string from the underlying resource bundle and format
      * it with the given set of arguments.
      *
-     * @param key  The key for the required message
-     * @param args The values to insert into the message
+     * @param key
+     * @param args
      */
     public String getString(final String key, final Object... args) {
         String value = getString(key);
@@ -162,7 +159,6 @@
         return mf.format(args, new StringBuffer(), null).toString();
     }
 
-
     /**
      * Identify the Locale this StringManager is associated with
      */
@@ -170,7 +166,6 @@
         return locale;
     }
 
-
     // --------------------------------------------------------------
     // STATIC SUPPORT METHODS
     // --------------------------------------------------------------
@@ -178,20 +173,6 @@
     private static final Map<String, Map<Locale,StringManager>> managers =
             new Hashtable<>();
 
-
-    /**
-     * Get the StringManager for a given class. The StringManager will be
-     * returned for the package in which the class is located. If a manager for
-     * that package already exists, it will be reused, else a new
-     * StringManager will be created and returned.
-     *
-     * @param clazz The class for which to retrieve the StringManager
-     */
-    public static final StringManager getManager(Class<?> clazz) {
-        return getManager(clazz.getPackage().getName());
-    }
-
-
     /**
      * Get the StringManager for a particular package. If a manager for
      * a package already exists, it will be reused, else a new
@@ -199,11 +180,11 @@
      *
      * @param packageName The package name
      */
-    public static final StringManager getManager(String packageName) {
+    public static final synchronized StringManager getManager(
+            String packageName) {
         return getManager(packageName, Locale.getDefault());
     }
 
-
     /**
      * Get the StringManager for a particular package and Locale. If a manager
      * for a package/Locale combination already exists, it will be reused, else
@@ -247,7 +228,6 @@
         return mgr;
     }
 
-
     /**
      * Retrieve the StringManager for a list of Locales. The first StringManager
      * found will be returned.
diff --git a/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java b/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
index 5fde5c3..a3004b8 100644
--- a/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
+++ b/java/org/apache/tomcat/websocket/CaseInsensitiveKeyMap.java
@@ -40,7 +40,7 @@
 public class CaseInsensitiveKeyMap<V> extends AbstractMap<String,V> {
 
     private static final StringManager sm =
-            StringManager.getManager(CaseInsensitiveKeyMap.class);
+            StringManager.getManager(Constants.PACKAGE_NAME);
 
     private final Map<Key,V> map = new HashMap<>();
 
diff --git a/modules/bayeux/.classpath b/modules/bayeux/.classpath
new file mode 100644
index 0000000..889b127
--- /dev/null
+++ b/modules/bayeux/.classpath
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="src" path="java"/>
+	<classpathentry kind="src" path="test"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry combineaccessrules="false" kind="src" path="/tomcat-trunk"/>
+	<classpathentry kind="var" path="TOMCAT_LIBS_BASE/json-20080701/json.jar"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/modules/bayeux/.project b/modules/bayeux/.project
new file mode 100644
index 0000000..872599e
--- /dev/null
+++ b/modules/bayeux/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>tomcat-bayeux</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/modules/bayeux/build.xml b/modules/bayeux/build.xml
new file mode 100644
index 0000000..d30ba7f
--- /dev/null
+++ b/modules/bayeux/build.xml
@@ -0,0 +1,228 @@
+<?xml version="1.0"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<project name="Tomcat 6.0" default="bayeux" basedir="../..">
+
+
+  <!-- ===================== Initialize Property Values =================== -->
+
+  <!-- See "build.properties.sample" in the top level directory for all     -->
+  <!-- property values you must customize for successful building!!!        -->
+  <property file="${user.home}/build.properties"/>
+  <property file="${basedir}/build.properties"/>
+  <property file="${basedir}/build.properties.default"/>
+
+  <!-- Project Properties -->
+  <property name="project"               value="apache-tomcat" />
+  <property name="name"                  value="Apache Tomcat" />
+  <property name="year"                  value="2008" />
+  <property name="version.major"         value="6" />
+  <property name="version.minor"         value="0" />
+  <property name="version.build"         value="0" />
+  <property name="version.patch"         value="0" />
+  <property name="version.suffix"        value="-dev" />
+
+  <property name="version"               value="${version.major}.${version.minor}.${version.build}${version.suffix}" />
+  <property name="version.number"        value="${version.major}.${version.minor}.${version.build}.${version.patch}" />
+  <property name="version.major.minor"   value="${version.major}.${version.minor}" />
+
+  <property name="final.name"            value="${project}-${version}" />
+  <property name="final-src.name"        value="${project}-${version}-src" />
+
+  <!-- Build Defaults -->
+  <property name="tomcat.build"      value="${basedir}/output/build"/>
+  <property name="tomcat.classes"    value="${basedir}/output/classes"/>
+  <property name="tomcat.dist"       value="${basedir}/output/dist"/>
+  <property name="tomcat.extras"     value="${basedir}/output/extras"/>
+  <property name="tomcat.deployer"   value="${basedir}/output/deployer"/>
+  <property name="tomcat.release"    value="${basedir}/output/release"/>
+  <property name="test.failonerror"  value="true"/>
+  <property name="test.runner"       value="junit.textui.TestRunner"/>
+
+  <!-- Can't be lower - jsp uses templates -->
+  <property name="compile.source" value="1.5"/>
+
+  <!-- JAR artifacts -->
+  <property name="cometd-api.jar" value="${tomcat.extras}/cometd-api.jar"/>
+  <property name="tomcat-bayeux.jar" value="${tomcat.extras}/tomcat-bayeux.jar"/>
+  <property name="cometd.war" value="${tomcat.extras}/cometd.war"/>
+  <property name="tomcat-bayeux-samples.jar" value="${tomcat.extras}/tomcat-bayeux-samples.jar"/>
+
+	<!-- Classpath -->
+  <path id="tomcat.classpath">
+    <pathelement location="${tomcat.classes}"/>
+  </path>
+
+  <target name="prepare">
+    <mkdir dir="${tomcat.extras}"/>
+  </target>
+
+  <target name="clean">
+  	<delete dir="${tomcat.extras}"/>
+  </target>
+
+
+	<target name="bayeux">
+    <mkdir dir="${tomcat.extras}"/>
+
+    <antcall target="downloadfile">
+      <param name="sourcefile" value="${json-lib.lib}"/>
+      <param name="destfile" value="${json-lib.home}/${json-lib.jar}"/>
+      <param name="destdir" value="${json-lib.home}"/>
+    </antcall>
+
+    <antcall target="downloadgz">
+      <param name="sourcefile" value="${dojo-js.loc}"/>
+      <param name="destfile" value="${dojo-js.jar}"/>
+    </antcall>
+  	
+    <copy todir="${tomcat.extras}" file="${json-lib.home}/${json-lib.jar}"/>
+    <!-- Classpath -->
+    <path id="tomcat.bayeux.classpath">
+      <pathelement path="${tomcat.classpath}"/>
+      <pathelement path="${json-lib.home}/${json-lib.jar}"/>
+    </path>
+
+    <!-- compile org.apache.tomcat.bayeux -->
+    <!-- compile org.apache.cometd -->
+    <javac srcdir="modules/bayeux/java" destdir="${tomcat.classes}"
+           debug="${compile.debug}"
+           deprecation="${compile.deprecation}"
+           source="${compile.source}"
+           encoding="ISO-8859-1">
+      <classpath refid="tomcat.bayeux.classpath" />
+      <include name="org/apache/tomcat/bayeux/**" />
+      <include name="org/apache/cometd/**" />
+    </javac>
+
+    <!-- Cometd API JAR File -->
+    <jar jarfile="${cometd-api.jar}">
+      <fileset dir="${tomcat.classes}">
+        <exclude name="**/package.html" />
+        <exclude name="**/LocalStrings_*" />
+        <include name="org/apache/cometd/**" />
+      </fileset>
+    </jar>
+    <!-- Cometd API JAR File -->
+    <jar jarfile="${tomcat-bayeux.jar}">
+      <fileset dir="${tomcat.classes}">
+        <exclude name="**/package.html" />
+        <exclude name="**/LocalStrings_*" />
+        <include name="org/apache/tomcat/bayeux/**" />
+      </fileset>
+    </jar>
+
+    <!-- cometd samples application -->
+    <javac srcdir="modules/bayeux/test" destdir="${tomcat.classes}"
+           debug="${compile.debug}"
+           deprecation="${compile.deprecation}"
+           source="${compile.source}"
+           encoding="ISO-8859-1">
+      <classpath refid="tomcat.bayeux.classpath" />
+      <include name="org/apache/tomcat/bayeux/**" />
+      <include name="org/apache/cometd/**" />
+    </javac>
+
+    <!-- Cometd samples JAR File -->
+    <jar jarfile="${tomcat-bayeux-samples.jar}">
+      <fileset dir="${tomcat.classes}">
+        <exclude name="**/package.html" />
+        <exclude name="**/LocalStrings_*" />
+        <include name="org/apache/cometd/bayeux/samples/**" />
+      </fileset>
+    </jar>
+
+    <!-- build samples webapplication /cometd -->
+  	<property name="cometd-app" value="${base.path}/cometd"/>
+  	<mkdir dir="${cometd-app}"/>
+  	
+  	<copy todir="${cometd-app}">
+  	  <fileset dir="${basedir}/modules/bayeux/webapps/cometd">
+  		<include name="**/**"/>
+  	  </fileset>
+      <fileset dir="${dojo-js.home}">
+      	<include name="dojo/**"/>
+      	<include name="dojox/**"/>
+      </fileset>	
+  	</copy>
+    <mkdir dir="${cometd-app}/WEB-INF/lib"/>
+    <copy todir="${cometd-app}/WEB-INF/lib" file="${tomcat-bayeux-samples.jar}"/>
+  	
+    <zip zipfile="${cometd.war}">
+      <fileset dir="${cometd-app}">
+        <include name="**/**"/>
+      </fileset>
+    </zip>
+  	
+  	<delete dir="${cometd-app}"/>
+  	
+  	<!-- create checksums -->
+    <checksum file="${cometd-api.jar}" forceOverwrite="yes" fileext=".md5" />
+    <checksum file="${tomcat-bayeux.jar}" forceOverwrite="yes" fileext=".md5" />
+    <checksum file="${cometd.war}" forceOverwrite="yes" fileext=".md5" />
+    <checksum file="${tomcat.extras}/${json-lib.jar}" forceOverwrite="yes" fileext=".md5" />
+  	
+  	<!-- print out how to -->
+    <echo>You've built the Tomcat Bayeux libraries, simply add the following libraries to your CATALINA_HOME/lib directory:
+          ${cometd-api.jar}
+          ${tomcat-bayeux.jar}
+          ${tomcat.extras}/${json-lib.jar}
+To run the sample application, copy the following applications into your CATALINA_BASE/webapps directory
+          ${cometd.war}
+    </echo>
+  </target>
+
+
+
+  <!-- Download and dependency building -->
+  <target name="proxyflags">
+    <!-- check proxy parameters. -->
+    <condition property="useproxy">
+      <equals arg1="${proxy.use}" arg2="on" />
+    </condition>
+  </target>
+
+  <target name="setproxy" depends="proxyflags" if="useproxy">
+    <taskdef name="setproxy"
+            classname="org.apache.tools.ant.taskdefs.optional.net.SetProxy" />
+    <setproxy proxyhost="${proxy.host}" proxyport="${proxy.port}"
+              proxyuser="${proxy.user}" proxypassword="${proxy.password}" />
+    <echo message="Using ${proxy.host}:${proxy.port} to download ${sourcefile}"/>
+  </target>
+
+  <target name="testexist">
+    <echo message="Testing  for ${destfile}"/>
+    <available file="${destfile}" property="exist"/>
+  </target>
+
+  <target name="downloadfile" unless="exist" depends="setproxy,testexist">
+    <!-- Download extract the file -->
+    <mkdir dir="${destdir}" />
+    <get src="${sourcefile}" dest="${destfile}" />
+  </target>
+	
+  <target name="downloadgz" unless="exist" depends="setproxy,testexist">
+    <!-- Download and extract the package -->
+    <get src="${sourcefile}" dest="${base.path}/file.tar.gz" />
+    <gunzip src="${base.path}/file.tar.gz" dest="${base.path}/file.tar"/>
+    <untar src="${base.path}/file.tar" dest="${base.path}"/>
+    <delete file="${base.path}/file.tar"/>
+    <delete file="${base.path}/file.tar.gz"/>
+  </target>
+
+
+</project>
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/Bayeux.java b/modules/bayeux/java/org/apache/cometd/bayeux/Bayeux.java
new file mode 100644
index 0000000..6c54fa6
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/Bayeux.java
@@ -0,0 +1,241 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+import java.util.List;
+
+/** Bayeux Interface.<br>
+ * This interface represents the server side API for the Bayeux messaging protocol.
+ * Bayeux is a simple subscribe/publish/receive methodology, not far from JMS, but much simplified.<br>
+ * It is used both by the actual implementation and by server side clients.<br>
+ * Server side clients use this to create, retrieve and subscribe to channels.
+ * Server side clients are represented, just like remote clients, through the Client interface.
+ * <br>
+ * The Bayeux implementations is intended to be thread safe and multiple threads may simultaneously call Bayeux methods.
+ * <br>
+ * The Bayeux object, is the starting point for any cometd application relying on the Bayeux object.
+ * Dependent on the container, the Bayeux object will be stored in the <code>javax.servlet.ServletContext</code> object
+ * as an attribute under the name <code>Bayeux.DOJOX_COMETD_BAYEUX</code><br>
+ * To retrieve this object, one would simply call<br>
+ * <code>Bayeux bx = (Bayeux)getServletContext().getAttribute(Bayeux.DOJOX_COMETD_BAYEUX);
+ * <br><br>
+ * The Bayeux protocol is pretty straight forward and includes a bunch of messaging that is not needed to be known to clients,
+ * both server side and remote clients.
+ * This object gets initialized by a container dependent servlet, and the servlet then handles all Bayeux communication from the client.
+ * Remote messsages are delivered to channels, and to server side clients using the <code>Listener</code> interface.<br>
+ * <br>
+ * A <code>Bayeux session</code> is active as long as the webapp hosting the Bayeux object is active.<br>
+ * When the webapplication shuts down, the Bayeux object will unsubscribe all clients and remove all the active channels.
+ *
+ * @author Greg Wilkins
+ */
+public interface Bayeux {
+
+    /**Meta definitions for channels*/
+    public static final String META="/meta";
+    /**Meta definitions for channels*/
+    public static final String META_SLASH="/meta/";
+    /**Meta definitions for channels - connect message*/
+    public static final String META_CONNECT="/meta/connect";
+    /**Meta definitions for channels - client messsage*/
+    public static final String META_CLIENT="/meta/client";
+    /**Meta definitions for channels - disconnect messsage*/
+    public static final String META_DISCONNECT="/meta/disconnect";
+    /**Meta definitions for channels - handshake messsage*/
+    public static final String META_HANDSHAKE="/meta/handshake";
+    /**Meta definitions for channels - ping messsage*/
+    public static final String META_PING="/meta/ping";
+    /**Meta definitions for channels - reconnect messsage
+     * @deprecated
+     */
+    public static final String META_RECONNECT="/meta/reconnect";
+    /**Meta definitions for channels - status messsage*/
+    public static final String META_STATUS="/meta/status";
+    /**Meta definitions for channels - subscribe messsage*/
+    public static final String META_SUBSCRIBE="/meta/subscribe";
+    /**Meta definitions for channels - unsubscribe messsage*/
+    public static final String META_UNSUBSCRIBE="/meta/unsubscribe";
+    /*Field names inside Bayeux messages*/
+    /**Field names inside Bayeux messages - clientId field*/
+    public static final String CLIENT_FIELD="clientId";
+    /**Field names inside Bayeux messages - data field*/
+    public static final String DATA_FIELD="data";
+    /**Field names inside Bayeux messages - channel field*/
+    public static final String CHANNEL_FIELD="channel";
+    /**Field names inside Bayeux messages - id field*/
+    public static final String ID_FIELD="id";
+    /**Field names inside Bayeux messages - error field*/
+    public static final String ERROR_FIELD="error";
+    /**Field names inside Bayeux messages - timestamp field*/
+    public static final String TIMESTAMP_FIELD="timestamp";
+    /**Field names inside Bayeux messages - transport field*/
+    public static final String TRANSPORT_FIELD="transport";
+    /**Field names inside Bayeux messages - advice field*/
+    public static final String ADVICE_FIELD="advice";
+    /**Field names inside Bayeux messages - successful field*/
+    public static final String SUCCESSFUL_FIELD="successful";
+    /**Field names inside Bayeux messages - subscription field*/
+    public static final String SUBSCRIPTION_FIELD="subscription";
+    /**Field names inside Bayeux messages - ext field*/
+    public static final String EXT_FIELD="ext";
+    /**Field names inside Bayeux messages - connectionType field*/
+    public static final String CONNECTION_TYPE_FIELD="connectionType";
+    /**Field names inside Bayeux messages - version field*/
+    public static final String VERSION_FIELD="version";
+    /**Field names inside Bayeux messages - minimumVersion field*/
+    public static final String MIN_VERSION_FIELD="minimumVersion";
+    /**Field names inside Bayeux messages - supportedConnectionTypes field*/
+    public static final String SUPP_CONNECTION_TYPE_FIELD="supportedConnectionTypes";
+    /**Field names inside Bayeux messages - json-comment-filtered field*/
+    public static final String JSON_COMMENT_FILTERED_FIELD="json-comment-filtered";
+    /**Field names inside Bayeux messages - reconnect field*/
+    public static final String RECONNECT_FIELD = "reconnect";
+    /**Field names inside Bayeux messages - interval field*/
+    public static final String INTERVAL_FIELD = "interval";
+    /**Field values inside Bayeux messages - retry response*/
+    public static final String RETRY_RESPONSE = "retry";
+    /**Field values inside Bayeux messages - handshake response*/
+    public static final String HANDSHAKE_RESPONSE = "handshake";
+    /**Field values inside Bayeux messages - none response*/
+    public static final String NONE_RESPONSE = "none";
+    /**Service channel names-starts with*/
+    public static final String SERVICE="/service";
+    /**Service channel names-trailing slash*/
+    public static final String SERVICE_SLASH="/service/";
+    /*Transport types*/
+    /**Transport types - long polling*/
+    public static final String TRANSPORT_LONG_POLL="long-polling";
+    /**Transport types - callback polling*/
+    public static final String TRANSPORT_CALLBACK_POLL="callback-polling";
+    /**Transport types - iframe*/
+    public static final String TRANSPORT_IFRAME="iframe";
+    /**Transport types - flash*/
+    public static final String TRANSPORT_FLASH="flash";
+    /** ServletContext attribute name used to obtain the Bayeux object */
+    public static final String DOJOX_COMETD_BAYEUX="dojox.cometd.bayeux";
+    /*http field names*/
+    /**http helpers - text/json content type*/
+    public static final String JSON_CONTENT_TYPE="text/json";
+    /**http helpers - parameter name for json message*/
+    public static final String MESSAGE_PARAMETER="message";
+    /**http helpers - name of the jsonp parameter*/
+    public static final String JSONP_PARAMETER="jsonp";
+    /**http helpers - default name of the jsonp callback function*/
+    public static final String JSONP_DEFAULT_NAME="jsonpcallback";
+
+    /*--Client----------------------------------------------------------- */
+    /**
+     * Creates a new server side client. This method is to be invoked
+     * by server side objects only. You cannot create a remote client by using this method.
+     * A client represents an entity that can subscribe to channels and publish and receive messages
+     * through these channels
+     * @param idprefix String - the prefix string for the id generated, can be null
+     * @param listener Listener - a callback object to be called when messages are to be delivered to the new client
+     * @return Client - returns an implementation of the client interface.
+     */
+    public Client newClient(String idprefix, Listener listener);
+
+    /**
+     * retrieve a client based on an ID. Will return null if the client doesn't exist.
+     * @param clientid String
+     * @return Client-null if the client doesn't exist.returns the client if it does.
+     */
+    public Client getClient(String clientid);
+
+    /**
+     * Returns a non modifiable list of all the clients that are currently active
+     * in this Bayeux session
+     * @return List<Client> - a list containing all clients. The List can not be modified.
+     */
+    public List<Client> getClients();
+
+    /**
+     * Returns true if a client with the given id exists.<br>
+     * Same as executing <code>getClient(id)!=null</code>.
+     * @param clientId String
+     * @return boolean - true if the client exists
+     */
+    public boolean hasClient(String clientId);
+
+    /**
+     * Removes the client all together.
+     * This will unsubscribe the client to any channels it may be subscribed to
+     * and remove it from the list.
+     * @param client Client
+     * @return Client - returns the client that was removed, or null if no client was removed.
+     */
+    public Client remove(Client client);
+
+
+    /*--Channel---------------------------------------------------------- */
+    /**
+     * Returns the channel for a given channel id.
+     * If the channel doesn't exist, and the <code>create</code> parameter is set to true,
+     * the channel will be created and added to the list of active channels.<br>
+     * if <code>create</code> is set to false, and the channel doesn't exist, null will be returned.
+     * @param channelId String - the id of the channel to be retrieved or created
+     * @param create boolean - true if the Bayeux impl should create the channel
+     * @return Channel - null if <code>create</code> is set to false and the channel doesn't exist,
+     * otherwise it returns a channel object.
+     */
+    public Channel getChannel(String channelId, boolean create);
+
+    /**
+     * Returns a list of currently active channels in this Bayeux session.
+     * @return List<Channel>
+     */
+    public List<Channel> getChannels();
+
+    /**
+     * Removes a channel from the Bayeux object.
+     * This will also unsubscribe all the clients currently subscribed to the
+     * the channel.
+     * @param channel Channel - the channel to be removed
+     * @return Channel - returns the channel that was removed, or null if no channel was removed.
+     */
+    public Channel remove(Channel channel);
+
+    /**
+     * returns true if a channel with the given channelId exists.
+     * <br>Same as executing <code>Bayeux.getChannel(channelId,false)!=null</code>
+     * @param channelId String
+     * @return boolean - true if the channel exists.
+     */
+    public boolean hasChannel(String channelId);
+
+    /* --Message---------------------------------------------------------- */
+    /**
+     * Creates a new message to be sent by a server side client.
+     * @return Message - returns a new Message object, that has a unique id.
+     */
+    public Message newMessage(Client from);
+
+
+    /*--Security policy----------------------------------------------------------- */
+    /**
+     * Returns the security policy associated with this Bayeux session
+     * @return SecurityPolicy
+     */
+    public SecurityPolicy getSecurityPolicy();
+
+    /**
+     * Sets the security policy to be used in this Bayeux session
+     * @param securityPolicy SecurityPolicy
+     */
+    public void setSecurityPolicy(SecurityPolicy securityPolicy);
+
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/Channel.java b/modules/bayeux/java/org/apache/cometd/bayeux/Channel.java
new file mode 100644
index 0000000..e5e45f9
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/Channel.java
@@ -0,0 +1,102 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+import java.util.List;
+
+/**
+ * A Bayeux Channel represents a channel used to receive messages from and to publish messages to.
+ * In order to publish messages to or receive messages from, one must subscribe to the channel.
+ * This is easily done by invoking the <code>subscribe</code> method.
+ * A channel is created by calling the <code>Bayeux.getChannel(channelId,true)</code> method.
+ * A channel can be created either server side by invoking the getChannel, or client side
+ * by using the /meta/subscribe message without a wildcard.
+ * @author Greg Wilkins
+ */
+public interface Channel
+{
+    /**
+     * Returns the id for this channel. The id is unique within bayeux session.
+     * @return String - will never be null.
+     */
+    public String getId();
+
+    /**
+     * Publishes a message to all the subscribers of this channel.
+     * The <code>from</code> is contained within the message, by calling
+     * <code>msg.getClient()</code>
+     * @param data - the message to be published, can not be null.
+     */
+    public void publish(Message msg);
+
+    /**
+     * Publishes more than one message to all the subscribers of this channel.
+     * The <code>from</code> is contained within the message, by calling
+     * <code>msg[x].getClient()</code>
+     * @param data - the message to be published, can not be null.
+     */
+    public void publish(Message[] msgs);
+
+    /**
+     * Non persistent channels are removed when the last subscription is
+     * removed. Persistent channels survive periods without any subscribers.
+     * @return true if the Channel will persist without any subscription.
+     */
+    public boolean isPersistent();
+
+    /**
+     * @param persistent true if the Channel will persist without any subscription.
+     * @see isPersistent
+     */
+    public void setPersistent(boolean persistent);
+
+    /**
+     * Subscribes a client to a channel.
+     * @param subscriber - the client to be subscribed. If the client
+     * already is subscribed, this call will not create a duplicate subscription.
+     */
+    public void subscribe(Client subscriber);
+
+    /**
+     * Unsubscribes a client from a channel
+     * @param subscriber - the client to be subscribed.
+     * @return - returns the client that was unsubscribed, or null if the client wasn't subscribed.
+     */
+    public Client unsubscribe(Client subscriber);
+
+    /**
+     * returns a non modifiable list of all the subscribers to this
+     * channel.
+     * @return a list of subscribers
+     */
+    public List<Client> getSubscribers();
+
+    /**
+     * Adds a data filter to this channel. All messages received by this channel
+     * will run through this filter.
+     * @param filter Filter
+     */
+    public void addFilter(DataFilter filter);
+
+    /**
+     * Removes a filter from this channel.
+     * returns the filter that was removed, or null if the filter wasn't in the channel.
+     * @param filter Filter
+     * @return Filter - null if no filter was removed otherwise it returns the filter that was removed.
+     */
+    public DataFilter removeFilter(DataFilter filter);
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/Client.java b/modules/bayeux/java/org/apache/cometd/bayeux/Client.java
new file mode 100644
index 0000000..36da967
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/Client.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+
+
+/** A Bayeux Client.
+ * <p>
+ * A client may subscribe to channels and publish messages to channels.
+ * Client instances should not be directly created by uses, but should
+ * be obtained via the {@link Bayeux#getClient(String)} or {@link Bayeux#newClient(String, Listener)}
+ * methods.
+ * </p>
+ * <p>
+ * Three types of client may be represented by this interface:<nl>
+ * <li>The server representation of a remote client connected via HTTP,
+ *     automatically created by the Bayeux server when a connect message comes in</li>
+ * <li>A server side client, created by the application using the {@link Bayeux#newClient(String, Listener)} method</li>
+ * <li>A java client connected to a remote Bayeux server - not implemented</li>
+ * </nl>
+ * @author Greg Wilkins
+ */
+public interface Client
+{
+    /**
+     * Returns a unique id for this client. The id is unique within this Bayeux session.
+     * @return String - will not be null
+     */
+    public String getId();
+
+    /**
+     * Returns true if this client is holding messages to be delivered to the remote client.
+     * This method always returns false for local clients, since messages are delivered instantly using the
+     * Listener(callback) object
+     * @return boolean
+     */
+    public boolean hasMessages();
+
+    /**
+     * Deliver a message to this client only
+     * Deliver a message directly to the client. The message is not
+     * filtered or published to a channel.
+     * @param message
+     */
+    public void deliver(Message message);
+
+    /**
+     * Deliver a batch of messages to this client only
+     * Deliver a batch messages directly to the client. The messages are not
+     * filtered or published to a channel.
+     * @param message
+     */
+    public void deliver(Message[] message);
+
+    /**
+     * @return True if the client is local. False if this client is either a remote HTTP client or
+     * a java client to a remote server.
+     */
+    public boolean isLocal();
+
+    /**
+     * Starts a batch, no messages will be delivered until endBatch is called.
+     * Batches can be nested, and messages will only be delivered after
+     * the last endBatch has been called.
+     */
+    public void startBatch();
+
+    /**
+     * Ends a batch. since batches can be nested, messages will only be delivered
+     * after the endBatch has been called as many times as startBatch has.
+     */
+    public void endBatch();
+
+
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/DataFilter.java b/modules/bayeux/java/org/apache/cometd/bayeux/DataFilter.java
new file mode 100644
index 0000000..8111a9c
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/DataFilter.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+/**
+ * Data Filter<br>
+ * Data filters are used to transform data as it is sent to a Channel.
+ * Messages are filtered as the message is published to a channel, invoking the
+ * {@link Channel#publish(Message)} method.<br>
+ * This method gets invoked in two different scenarios, the first being when a message is received from
+ * a remote client, and the Bayeux implementation invokes the publish method directly.
+ * The second scenario is when a local client invokes {@link Channel#publish(Message)} directly in the local JVM.
+ * @author Greg Wilkins
+ *
+ */
+public interface DataFilter
+{
+    /**
+     * Runs a message through the filter. Filtering can only modify an existing object, it can not replace it.
+     * @param data Message - the message to be filtered, may not be null
+     */
+    public void filter(Message data);
+}
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/Listener.java b/modules/bayeux/java/org/apache/cometd/bayeux/Listener.java
new file mode 100644
index 0000000..089a8bd
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/Listener.java
@@ -0,0 +1,44 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+/**
+ * Cometd Listener interface.<br>
+ * For local clients, in order to receive messages, they pass in a callback object
+ * when the local client is created using the {@link Bayeux#newClient(String,Listener)} method.
+ * This callback object, implementing the Listener interface, is used to deliver messages to local, in JVM, clients.
+ * @author Greg Wilkins
+ *
+ */
+public interface Listener
+{
+    /**
+     * This method is called when the client is removed (explicitly or from a timeout)
+     * @param timeout - true if the client was removed from a timeout
+     * false if it was removed explicitly.
+     */
+    public void removed(boolean timeout);
+
+    /**
+     * Invoked when a message is delivered to the client.
+     * The message contains the message itself, as well as what channel this message came through
+     * and who the sender is. If someone invoked {@link Client#deliver(Message)} then the channel reference will
+     * be null.
+     * @param msg
+     */
+    public void deliver(Message[] msg);
+}
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/Message.java b/modules/bayeux/java/org/apache/cometd/bayeux/Message.java
new file mode 100644
index 0000000..3544561
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/Message.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+import java.util.Map;
+
+/**
+ * A Bayeux Message<br>
+ * A Bayeux message is a Map of String/Object key value pairs representing the data in the message.
+ * The message contains information about the channel it was published through and who the sender was
+ *
+ * @author Greg Wilkins
+ */
+public interface Message extends Map<String,Object>
+{
+    /**
+     * Returns a reference to the client that sent this message
+     * @return Client - may be null
+     */
+    public Client getClient();
+    /**
+     * Returns a reference to the channel that this message was published throuhg
+     * @return Channel - may be null
+     */
+    public Channel getChannel();
+    /**
+     * Returns the unique id of this message
+     * @return String
+     */
+    public String getId();
+
+    /**
+     * Sets the time to live in milliseconds. If the message hasn't been delivered
+     * when the time passed after the creation time is longer than the TTL the message will
+     * expire and removed from any delivery queues.
+     * @param ttl long
+     */
+    public void setTTL(long ttl);
+
+    /**
+     * Returns the time to live (in milliseconds) for this message
+     * @return long
+     */
+    public long getTTL();
+
+    /**
+     * returns the timestamp in milliseconds(System.currentTimeMillis()) of when this message was created.
+     * @return long
+     */
+    public long getCreationTime();
+}
+
+
diff --git a/modules/bayeux/java/org/apache/cometd/bayeux/SecurityPolicy.java b/modules/bayeux/java/org/apache/cometd/bayeux/SecurityPolicy.java
new file mode 100644
index 0000000..e7bc38a
--- /dev/null
+++ b/modules/bayeux/java/org/apache/cometd/bayeux/SecurityPolicy.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.cometd.bayeux;
+
+/**
+ * @author Greg Wilkins
+ */
+public interface SecurityPolicy
+{
+    boolean canHandshake(Message message);
+    boolean canCreate(Client client,String channel,Message message);
+    boolean canSubscribe(Client client,String channel,Message messsage);
+    boolean canPublish(Client client,String channel,Message messsage);
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxException.java b/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxException.java
new file mode 100644
index 0000000..8794e01
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxException.java
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+/**
+ *
+ * @version 1.0
+ */
+public class BayeuxException extends Exception {
+    public BayeuxException() {
+        super();
+    }
+
+    public BayeuxException(String message) {
+        super(message);
+    }
+
+    public BayeuxException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public BayeuxException(Throwable cause) {
+        super(cause);
+    }
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxRequest.java
new file mode 100644
index 0000000..43e2b4e
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxRequest.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import org.apache.tomcat.bayeux.HttpError;
+
+/**
+ * An interface that defines methods for managing Bayeux request meta
+ * messages.
+ *
+ * @author Guy A. Molinari
+ * @version 0.9
+ */
+public interface BayeuxRequest {
+
+    public static final String LAST_REQ_ATTR = "org.apache.cometd.bayeux.last_request";
+    public static final String CURRENT_REQ_ATTR = "org.apache.cometd.bayeux.current_request";
+    public static final String JSON_MSG_ARRAY = "org.apache.cometd.bayeux.json_msg_array";
+
+    /**
+     * Validates a specific request.
+     * This method must be called prior to process()
+     * as a request can do pre processing in the validate method.
+     * <br>
+     * Should the validation fail, an error object is returned
+     * containing an error message, and potentially a stack trace
+     * if an exception was generated
+     * @return HttpError - null if no error was detected, an HttpError object containing information about the error.
+     */
+    public HttpError validate();
+
+    /**
+     * processes a remote client Bayeux message
+     * @param prevops - the operation requested by the previous request, in case of chained requests.
+     * @return int - returns the interest operation for a CometEvent. Currently not used
+     * @throws BayeuxException - if an error was detected, and the appropriate error response couldn't be delivered to the client.
+     */
+    public int process(int prevops) throws BayeuxException;
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxServlet.java b/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxServlet.java
new file mode 100644
index 0000000..36fd065
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/BayeuxServlet.java
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import java.io.IOException;
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometProcessor;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+
+/**
+ *
+ * @author Guy Molinari
+ * @version 1.0
+ */
+public class BayeuxServlet implements CometProcessor {
+
+    /**
+     * Attribute to hold the TomcatBayeux object in the servlet context
+     */
+    public static final String TOMCAT_BAYEUX_ATTR = Bayeux.DOJOX_COMETD_BAYEUX;
+
+    /**
+     * Logger object
+     */
+    private static final Log log = LogFactory.getLog(BayeuxServlet.class);
+
+    /**
+     * Servlet config - for future use
+     */
+    protected ServletConfig servletConfig;
+
+    /**
+     * Reference to the global TomcatBayeux object
+     */
+    protected TomcatBayeux tb;
+
+    /**
+     * Upon servlet destruction, the servlet will clean up the
+     * TomcatBayeux object and terminate any outstanding events.
+     */
+    public void destroy() {
+        servletConfig = null;
+        //to do, close all outstanding comet events
+        //tb.destroy();
+        tb = null;//TO DO, close everything down
+
+    }
+
+    /**
+     * Returns the preconfigured connection timeout.
+     * If no timeout has been configured as a servlet init parameter named <code>timeout</code>
+     * then the default of 2min will be used.
+     * @return int - the timeout for a connection in milliseconds
+     */
+    protected int getTimeout() {
+        String timeoutS = servletConfig.getInitParameter("timeout");
+        int timeout = 120*1000; //2 min
+        try {
+            timeout = Integer.parseInt(timeoutS);
+        }catch (NumberFormatException nfe) {
+            //ignore, we have a default value
+        }
+        return timeout;
+    }
+
+    protected int getReconnectInterval() {
+        String rs = servletConfig.getInitParameter("reconnectInterval");
+        int rct = 1000; //1 seconds
+        try {
+            rct = Integer.parseInt(rs);
+        }catch (NumberFormatException nfe) {
+            //ignore, we have a default value
+        }
+        return rct;
+    }
+
+
+    public void event(CometEvent cometEvent) throws IOException, ServletException {
+        CometEvent.EventType type = cometEvent.getEventType();
+        if (log.isDebugEnabled()) {
+            log.debug("["+Thread.currentThread().getName()+"] Received Comet Event type="+type+" subtype:"+cometEvent.getEventSubType());
+        }
+        synchronized (cometEvent) {
+            if (type==CometEvent.EventType.BEGIN) {
+                //begin event, set the timeout
+                cometEvent.setTimeout(getTimeout());
+                //checkBayeux(cometEvent); - READ event should always come
+            } else if (type==CometEvent.EventType.READ) {
+                checkBayeux(cometEvent);
+            } else if (type==CometEvent.EventType.ERROR) {
+                tb.remove(cometEvent);
+                cometEvent.close();
+            } else if (type==CometEvent.EventType.END) {
+                tb.remove(cometEvent);
+                cometEvent.close();
+            }//end if
+
+        }//synchronized
+    }//event
+
+    /**
+     *
+     * @param cometEvent CometEvent
+     * @return boolean - true if we comet event stays open
+     * @throws IOException
+     * @throws UnsupportedOperationException
+     */
+    protected void checkBayeux(CometEvent cometEvent) throws IOException, UnsupportedOperationException {
+        //we actually have data.
+        //data can be text/json or
+        if (Bayeux.JSON_CONTENT_TYPE.equals(cometEvent.getHttpServletRequest().getContentType())) {
+            //read and decode the bytes according to content length
+            log.warn("["+Thread.currentThread().getName()+"] JSON encoding not supported, will throw an exception and abort the request.");
+            int contentlength = cometEvent.getHttpServletRequest().getContentLength();
+            throw new UnsupportedOperationException("Decoding "+Bayeux.JSON_CONTENT_TYPE+" not yet implemented.");
+        } else { //GET method or application/x-www-form-urlencoded
+            String message = cometEvent.getHttpServletRequest().getParameter(Bayeux.MESSAGE_PARAMETER);
+            if (log.isTraceEnabled()) {
+                log.trace("["+Thread.currentThread().getName()+"] Received JSON message:"+message);
+            }
+            try {
+                int action = handleBayeux(message, cometEvent);
+                if (log.isDebugEnabled()) {
+                    log.debug("["+Thread.currentThread().getName()+"] Bayeux handling complete, action result="+action);
+                }
+                if (action<=0) {
+                    cometEvent.close();
+                }
+            }catch (Exception x) {
+                x.printStackTrace();
+                tb.remove(cometEvent);
+                log.error(x);
+                cometEvent.close();
+            }
+        }
+    }
+
+    protected int handleBayeux(String message, CometEvent event) throws IOException, ServletException {
+        int result = 0;
+        if (message==null || message.length()==0) return result;
+        try {
+            BayeuxRequest request = null;
+            //a message can be an array of messages
+            JSONArray jsArray = new JSONArray(message);
+            for (int i = 0; i < jsArray.length(); i++) {
+                JSONObject msg = jsArray.getJSONObject(i);
+
+                if (log.isDebugEnabled()) {
+                    log.debug("["+Thread.currentThread().getName()+"] Processing bayeux message:"+msg);
+                }
+                request = RequestFactory.getRequest(tb,event,msg);
+                if (log.isDebugEnabled()) {
+                    log.debug("["+Thread.currentThread().getName()+"] Processing bayeux message using request:"+request);
+                }
+                result = request.process(result);
+                if (log.isDebugEnabled()) {
+                    log.debug("["+Thread.currentThread().getName()+"] Processing bayeux message result:"+result);
+                }
+            }
+            if (result>0 && request!=null) {
+                event.getHttpServletRequest().setAttribute(BayeuxRequest.LAST_REQ_ATTR, request);
+                ClientImpl ci = (ClientImpl)tb.getClient(((RequestBase)request).getClientId());
+                ci.addCometEvent(event);
+                if (log.isDebugEnabled()) {
+                    log.debug("["+Thread.currentThread().getName()+"] Done bayeux message added to request attribute");
+                }
+            } else if (result == 0 && request!=null) {
+                RequestBase.deliver(event,(ClientImpl)tb.getClient(((RequestBase)request).getClientId()));
+                if (log.isDebugEnabled()) {
+                    log.debug("["+Thread.currentThread().getName()+"] Done bayeux message, delivered to client");
+                }
+            }
+
+        }catch (JSONException x) {
+            log.error(x);//to do impl error handling
+            result = -1;
+        }catch (BayeuxException x) {
+            log.error(x); //to do impl error handling
+            result = -1;
+        }
+        return result;
+    }
+
+    public ServletConfig getServletConfig() {
+        return servletConfig;
+    }
+
+    public String getServletInfo() {
+        return "Tomcat/BayeuxServlet/1.0";
+    }
+
+    public void init(ServletConfig servletConfig) throws ServletException {
+
+        this.servletConfig = servletConfig;
+        ServletContext ctx = servletConfig.getServletContext();
+        if (ctx.getAttribute(TOMCAT_BAYEUX_ATTR)==null)
+            ctx.setAttribute(TOMCAT_BAYEUX_ATTR,new TomcatBayeux());
+        this.tb = (TomcatBayeux)ctx.getAttribute(TOMCAT_BAYEUX_ATTR);
+        tb.setReconnectInterval(getReconnectInterval());
+    }
+
+    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
+        if (servletResponse instanceof HttpServletResponse) {
+            ( (HttpServletResponse) servletResponse).sendError(500, "Misconfigured Tomcat server, must be configured to support Comet operations.");
+        } else {
+            throw new ServletException("Misconfigured Tomcat server, must be configured to support Comet operations for the Bayeux protocol.");
+        }
+    }
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/ChannelImpl.java b/modules/bayeux/java/org/apache/tomcat/bayeux/ChannelImpl.java
new file mode 100644
index 0000000..f8c1a69
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/ChannelImpl.java
@@ -0,0 +1,188 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import java.util.LinkedList;
+
+import org.apache.cometd.bayeux.Channel;
+import org.apache.cometd.bayeux.Client;
+import org.apache.cometd.bayeux.DataFilter;
+import java.util.Collections;
+import java.util.List;
+import org.apache.cometd.bayeux.Message;
+import java.util.Iterator;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+/**
+ *
+ * @version 1.0
+ */
+public class ChannelImpl implements Channel {
+
+    private static final Log log = LogFactory.getLog(ChannelImpl.class);
+
+    /**
+     * The unique id of this channel
+     */
+    protected String id = null;
+
+    /**
+     * A list of the current subscribers
+     */
+    protected LinkedList<Client> subscribers = new LinkedList<Client>();
+
+    /**
+     * A list of the current filters
+     */
+    protected LinkedList<DataFilter> filters = new LinkedList<DataFilter>();
+
+    /**
+     * Is this channel persistent, default value is true
+     */
+    protected boolean persistent = true;
+
+    /**
+     * Creates a new channel
+     * @param id String - the id of the channel, can not be null
+     */
+    protected ChannelImpl(String id) {
+        assert id != null;
+        this.id = id;
+    }
+
+    /**
+     * returns the id of this channel
+     * @return String
+     */
+    public String getId() {
+        return id;
+    }
+
+    /**
+     * Returns true if this channel matches the pattern to its id.
+     * The channel pattern can be a complete name like <code>/service/mychannel</code>
+     * or it can be a wild card pattern like <code>/service/app2/**</code>
+     * @param pattern String according to the Bayeux specification section 2.2.1 Channel Globbing, can not be null.
+     * @return boolean true if the id of this channel matches the pattern
+     */
+    public boolean matches(String pattern) {
+        if (pattern == null)
+            throw new NullPointerException("Channel pattern must not be null.");
+        if (getId().equals(pattern))
+            return true;
+        int wildcardPos = pattern.indexOf("/*");
+        if (wildcardPos == -1)
+            return false;
+        boolean multiSegment = pattern.indexOf("**") != -1;
+        String leadSubstring = pattern.substring(0, wildcardPos);
+        if (leadSubstring == null)
+            return false;
+        if (multiSegment)
+            return getId().startsWith(leadSubstring);
+        else {
+            if (getId().length() <= wildcardPos + 2)
+                return false;
+            return !(getId().substring(wildcardPos + 2).contains("/"));
+        }
+    }
+
+
+
+    /**
+     * @return returns a non modifiable list of the subscribers for this channel.
+     */
+    public List<Client> getSubscribers() {
+        return Collections.unmodifiableList(subscribers);
+    }
+
+    /**
+     * @return true if the Channel will persist without any subscription.
+     */
+    public boolean isPersistent() {
+        return persistent;
+    }
+
+    public void publish(Message msg) {
+        publish(new Message[] {msg});
+    }
+
+    public void publish(Message[] msgs) {
+        if (msgs==null) return;
+        MessageImpl[] imsgs = new MessageImpl[msgs.length];
+        for (int i=0; msgs!=null && i<msgs.length; i++) {
+            Message data = msgs[i];
+
+            if (!(data instanceof MessageImpl))
+                throw new IllegalArgumentException("Invalid message class, you can only publish messages "+
+                                                   "created through the Bayeux.newMessage() method");
+            if (log.isDebugEnabled()) {
+                log.debug("Publishing message:"+data+" to channel:"+this);
+            }
+            //clone it so that we can set this channel as a reference
+            MessageImpl msg = (MessageImpl)((MessageImpl)data).clone();
+            //this is the channel it was delivered through
+            msg.setChannel(this);
+            //pass through filters
+            for (Iterator<DataFilter> it = filters.iterator(); it.hasNext(); ) {
+                it.next().filter(msg);
+            }
+            imsgs[i] = msg;
+        }
+        //deliver it to the clients
+        for (Iterator<Client> it = subscribers.iterator(); it.hasNext(); ) {
+            ClientImpl c = (ClientImpl)it.next();
+            c.deliverInternal(this,imsgs);
+        }
+
+    }
+
+    public void setPersistent(boolean persistent) {
+        this.persistent = persistent;
+    }
+
+    public void subscribe(Client subscriber) {
+        if (!subscribers.contains((subscriber))) {
+            subscribers.addLast(subscriber);
+            ((ClientImpl)subscriber).subscribed(this);
+        }
+    }
+
+    public Client unsubscribe(Client subscriber) {
+        if (subscribers.remove(subscriber)) {
+            ((ClientImpl)subscriber).unsubscribed(this);
+            return subscriber;
+        } else
+            return null;
+    }
+
+    public void addFilter(DataFilter filter) {
+        if (!filters.contains(filter))
+            filters.addLast(filter);
+    }
+
+    public DataFilter removeFilter(DataFilter filter) {
+        if ( filters.remove(filter) ) return filter;
+        else return null;
+    }
+
+    public String toString() {
+        StringBuilder buf = new StringBuilder(super.toString());
+        buf.append("; channelId=").append(getId());
+        return buf.toString();
+    }
+
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/ClientImpl.java b/modules/bayeux/java/org/apache/tomcat/bayeux/ClientImpl.java
new file mode 100644
index 0000000..b18e0fa
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/ClientImpl.java
@@ -0,0 +1,279 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.concurrent.ConcurrentLinkedQueue;
+
+import org.apache.catalina.comet.CometEvent;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.cometd.bayeux.Client;
+import org.apache.cometd.bayeux.Listener;
+import org.apache.cometd.bayeux.Message;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.HashMap;
+import java.util.ArrayList;
+
+public class ClientImpl implements Client {
+
+    public static final int SUPPORT_CALLBACK_POLL = 0x1;
+    public static final int SUPPORT_LONG_POLL = 0x2;
+
+    public static final String COMET_EVENT_ATTR = "org.apache.cometd.bayeux.client";
+
+    private static final Log log = LogFactory.getLog(ClientImpl.class);
+
+    protected static LinkedList<Message> EMPTY_LIST = new LinkedList<Message>();
+    /**
+     * queued message for remote clients.
+     */
+    protected LinkedList<Message> messages = null;
+
+    /**
+     *
+     */
+    protected Queue<CometEvent> events = new LinkedList<CometEvent>();
+
+    /**
+     * Unique id representing this client
+     */
+    protected String id;
+
+    /**
+     * supported connection types, defaults to long-polling
+     */
+    protected int supportedConnTypes = SUPPORT_LONG_POLL | SUPPORT_CALLBACK_POLL;
+
+    /**
+     * The desired connection type
+     */
+    protected int desirectConnType = SUPPORT_LONG_POLL;
+
+    /**
+     * Does this client use json-comment-filtered messages
+     */
+    protected boolean useJsonFiltered = false;
+
+    /**
+     * Same JVM clients, get local=true
+     */
+    protected boolean local;
+
+    /**
+     * The callback object for local clients
+     */
+    protected Listener listener;
+
+    protected AtomicInteger nrofsubscriptions = new AtomicInteger(0);
+
+    protected ClientImpl(String id, boolean local) {
+        this.id = id;
+        this.local = local;
+        if (!local) messages = new LinkedList<Message>();
+    }
+
+    protected ClientImpl(String id, CometEvent event) {
+        this(id,false);
+        events = new ConcurrentLinkedQueue<CometEvent>();
+        addCometEvent(event);
+    }
+
+    public synchronized void deliver(Message message) {
+        deliverInternal(null,new MessageImpl[] {(MessageImpl)message});
+    }
+
+    public synchronized void deliver(Message[] message) {
+        deliverInternal(null,message);
+    }
+
+    protected synchronized void deliverInternal(ChannelImpl channel, MessageImpl message) {
+        deliverInternal(channel,new MessageImpl[] {message});
+    }
+
+    protected synchronized void deliverInternal(ChannelImpl channel, Message[] msgs) {
+        if (isLocal()) {
+            //local clients must have a listener
+            ArrayList<Message> list = new ArrayList<Message>();
+            for (int i=0; msgs!=null && i<msgs.length; i++) {
+                //dont deliver to ourselves
+                if (this!=msgs[i].getClient()) list.add(msgs[i]);
+            }
+            if (getListener() != null && list.size()>0) {
+                getListener().deliver(list.toArray(new Message[0]));
+            }
+        } else {
+            for (int i=0; msgs!=null && i<msgs.length; i++) {
+                MessageImpl message = (MessageImpl)msgs[i];
+                if (this==message.getClient()) {
+                    //dont deliver to ourself
+                    continue;
+                }
+                //we are not implementing forever responses, if the client is connected
+                //then we will fire off the message
+                //first we check to see if we have any existing connections we can piggy back on
+                CometEvent event = events.poll();
+                boolean delivered = false;
+                //TODO TODO - check on thread safety, for writing and for getting last request.
+                if (event!=null) {
+                    synchronized (event) {
+                        RequestBase rq = (RequestBase)event.getHttpServletRequest().getAttribute(RequestBase.LAST_REQ_ATTR);
+                        if (rq!=null) {
+                            Map map = new HashMap();
+                            try {
+                                map.put(Bayeux.CHANNEL_FIELD,message.getChannel().getId());
+                                map.put(Bayeux.DATA_FIELD,message);
+                                JSONObject json = new JSONObject(map);
+                                if (log.isDebugEnabled()) {
+                                    log.debug("Message instantly delivered to remote client["+this+"] message:"+json);
+                                }
+                                rq.addToDeliveryQueue(this, json);
+                                //deliver the batch
+                                if (i==(msgs.length-1)) {
+                                    rq.deliver(event, this);
+                                    event.close(); //todo, figure out a better way, this means only one message gets delivered
+                                    removeCometEvent(event); //and delivered instantly
+                                }
+                                delivered = true;
+                            } catch (Exception x) {
+                                log.error(x);
+                            }
+                        }
+                    }
+                }
+                if (!delivered) {
+                    if (log.isDebugEnabled()) {
+                        log.debug("Message added to queue for remote client["+this+"] message:"+message);
+                    }
+                    //queue the message for the next round
+                    messages.add(message);
+                }
+            }
+        }
+    }
+
+    public String getId() {
+        return this.id;
+    }
+
+    protected Listener getListener() {
+        return listener;
+    }
+
+    public boolean hasMessages() {
+        if (isLocal()) return false;
+        else {
+            return messages.size() > 0;
+        }
+    }
+
+    public boolean isLocal() {
+        return local;
+    }
+
+    public int getSupportedConnTypes() {
+        return supportedConnTypes;
+    }
+
+    public int getDesirectConnType() {
+        return desirectConnType;
+    }
+
+    public boolean useJsonFiltered() {
+        return useJsonFiltered;
+    }
+
+    public void setListener(Listener listener) {
+        this.listener = listener;
+    }
+
+    public void setSupportedConnTypes(int supportedConnTypes) {
+        this.supportedConnTypes = supportedConnTypes;
+    }
+
+    public void setUseJsonFiltered(boolean useJsonFiltered) {
+        this.useJsonFiltered = useJsonFiltered;
+    }
+
+    public void setDesirectConnType(int desirectConnType) {
+        this.desirectConnType = desirectConnType;
+    }
+
+    public boolean supportsCallbackPoll() {
+        return (supportedConnTypes & SUPPORT_CALLBACK_POLL) == SUPPORT_CALLBACK_POLL;
+    }
+
+    public boolean supportsLongPoll() {
+        return (supportedConnTypes & SUPPORT_LONG_POLL) == SUPPORT_LONG_POLL;
+    }
+
+    public synchronized List<Message> takeMessages() {
+        if (isLocal()) return null;
+        if (messages.size()==0) return EMPTY_LIST;
+        List result = new LinkedList(messages);
+        messages.clear();
+        return result;
+    }
+
+    public String toString() {
+        StringBuilder buf = new StringBuilder(super.toString());
+        buf.append(" id=").append(getId());
+        return buf.toString();
+    }
+
+    public boolean isSubscribed() {
+        return nrofsubscriptions.get()>0;
+    }
+
+    protected synchronized boolean addCometEvent(CometEvent event) {
+        boolean result = false;
+        if (!events.contains(event)) {
+            events.add(event);
+            result = true;
+        }
+        event.getHttpServletRequest().setAttribute(COMET_EVENT_ATTR,this);
+        return result;
+    }
+
+    protected synchronized boolean removeCometEvent(CometEvent event) {
+        boolean result = events.remove(event);
+        event.getHttpServletRequest().removeAttribute(COMET_EVENT_ATTR);
+        return result;
+    }
+
+
+    protected void subscribed(ChannelImpl ch) {
+        nrofsubscriptions.addAndGet(1);
+    }
+
+    protected void unsubscribed(ChannelImpl ch) {
+        nrofsubscriptions.addAndGet(-1);
+    }
+
+    public void startBatch(){
+        //noop until improved
+    }
+    public void endBatch() {
+        //noop until improved
+    }
+
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/HttpError.java b/modules/bayeux/java/org/apache/tomcat/bayeux/HttpError.java
new file mode 100644
index 0000000..57d5636
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/HttpError.java
@@ -0,0 +1,60 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.bayeux;
+
+public class HttpError {
+    private int code;
+    private String status;
+    private Throwable cause;
+    public HttpError(int code, String status, Throwable cause) {
+        this.code = code;
+        this.status = status;
+        this.cause = cause;
+    }
+
+    public void setCode(int code) {
+        this.code = code;
+    }
+
+    public void setStatus(String status) {
+        this.status = status;
+    }
+
+    public void setCause(Throwable exception) {
+        this.cause = exception;
+    }
+
+    public int getCode() {
+        return code;
+    }
+
+    public String getStatus() {
+        return status;
+    }
+
+    public Throwable getCause() {
+        return cause;
+    }
+
+    public String toString() {
+        if (cause != null)
+            return code + ":" + status + " - [" + cause + "]";
+        else
+            return code + ":" + status;
+    }
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/MessageImpl.java b/modules/bayeux/java/org/apache/tomcat/bayeux/MessageImpl.java
new file mode 100644
index 0000000..bf54652
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/MessageImpl.java
@@ -0,0 +1,80 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import java.util.HashMap;
+
+import org.apache.cometd.bayeux.Channel;
+import org.apache.cometd.bayeux.Client;
+import org.apache.cometd.bayeux.Message;
+
+public class MessageImpl extends HashMap<String,Object> implements Message {
+
+    protected Channel channel;
+    protected Client client;
+    protected String id;
+    private long TTL = 1000*60*5; //5min is the default TTL for a message
+    protected long creationTime = System.currentTimeMillis();
+
+    public Object clone() {
+        MessageImpl copy = new MessageImpl(id);
+        copy.putAll(this);
+        copy.channel = channel;
+        copy.client = client;
+        copy.id = id;
+        copy.creationTime = creationTime;
+        copy.TTL = TTL;
+        return copy;
+    }
+
+    protected MessageImpl(String id) {
+        assert id != null;
+        this.id = id;
+    }
+
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public Client getClient() {
+        return client;
+    }
+
+    public long getCreationTime() {
+        return creationTime;
+    }
+
+    public long getTTL() {
+        return TTL;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    protected void setChannel(Channel channel) {
+        this.channel = channel;
+    }
+
+    protected void setClient(Client client) {
+        this.client = client;
+    }
+
+    public void setTTL(long TTL) {
+        this.TTL = TTL;
+    }
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/RequestBase.java b/modules/bayeux/java/org/apache/tomcat/bayeux/RequestBase.java
new file mode 100644
index 0000000..f2ccfb1
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/RequestBase.java
@@ -0,0 +1,258 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.TimeZone;
+import java.util.Date;
+import java.text.SimpleDateFormat;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.cometd.bayeux.Message;
+
+/**
+ * Common functionality and member variables for all Bayeux requests.
+ *
+ * @author Guy A. Molinari
+ * @version 0.9
+ *
+ */
+public abstract class RequestBase implements BayeuxRequest {
+
+    protected static final SimpleDateFormat timestampFmt =
+        new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ");
+    static {
+        timestampFmt.setTimeZone(TimeZone.getTimeZone("GMT"));
+    }
+    //message properties, combined for all messages
+    protected TomcatBayeux tomcatBayeux;
+    protected String channel;
+    protected String id;
+    protected String clientId;
+    protected String version = null;
+    protected String[] suppConnTypes = null;
+    protected int suppConnTypesFlag = 0;
+    protected int desiredConnTypeFlag = 0;
+    protected String minVersion = null;
+    protected String subscription = null;
+    protected String data = null;
+    protected String conType = null;
+    protected LinkedHashMap<String, Object> ext = new LinkedHashMap<String, Object> ();
+
+
+    protected CometEvent event;
+
+    protected HashMap<String, Object> response = null;
+
+    private static final Log log = LogFactory.getLog(RequestBase.class);
+
+    protected int reconnectInterval = 1000;
+
+    protected RequestBase(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
+        this.tomcatBayeux = tb;
+        this.event = event;
+        channel = jsReq.optString(Bayeux.CHANNEL_FIELD);
+        id = jsReq.optString(Bayeux.ID_FIELD);
+        clientId = jsReq.optString(Bayeux.CLIENT_FIELD);
+        version = jsReq.optString(Bayeux.VERSION_FIELD);
+        minVersion = jsReq.optString(Bayeux.MIN_VERSION_FIELD);
+        conType = jsReq.optString(Bayeux.CONNECTION_TYPE_FIELD);
+        subscription = jsReq.optString(Bayeux.SUBSCRIPTION_FIELD);
+        data = jsReq.optString(Bayeux.DATA_FIELD);
+        reconnectInterval = tb.getReconnectInterval();
+        if (jsReq.has(Bayeux.EXT_FIELD)) {
+            JSONObject jext = jsReq.getJSONObject(Bayeux.EXT_FIELD);
+            for (Iterator<String> i = jext.keys(); i.hasNext(); ) {
+                String key = i.next();
+                ext.put(key, jext.get(key));
+            }//for
+        }//end if
+
+        if (jsReq.has(Bayeux.SUPP_CONNECTION_TYPE_FIELD)) {
+            JSONArray types = jsReq.getJSONArray(Bayeux.SUPP_CONNECTION_TYPE_FIELD);
+            suppConnTypes = new String[types.length()];
+            for (int i = 0; i < types.length(); i++) {
+                suppConnTypes[i] = types.getString(i);
+                if (Bayeux.TRANSPORT_CALLBACK_POLL.equals(suppConnTypes[i]))
+                    suppConnTypesFlag = suppConnTypesFlag|ClientImpl.SUPPORT_CALLBACK_POLL;
+                else if (Bayeux.TRANSPORT_LONG_POLL.equals(suppConnTypes[i]))
+                    suppConnTypesFlag = suppConnTypesFlag|ClientImpl.SUPPORT_LONG_POLL;
+            }//for
+        }//end if
+
+        if (conType!=null) {
+            if (Bayeux.TRANSPORT_CALLBACK_POLL.equals(conType))
+                desiredConnTypeFlag = ClientImpl.SUPPORT_CALLBACK_POLL;
+            else if (Bayeux.TRANSPORT_LONG_POLL.equals(conType))
+                desiredConnTypeFlag = ClientImpl.SUPPORT_LONG_POLL;
+        }//end if
+
+        //due to the fact that the javascript doesn't send up a required field
+        //we have to fake it
+        suppConnTypesFlag = ClientImpl.SUPPORT_CALLBACK_POLL | ClientImpl.SUPPORT_LONG_POLL;
+
+    }
+
+    public HttpError validate() {
+        HttpError result = null;
+//        if (clientId == null) {
+//            result = new HttpError(401,"No Client ID.", null);
+//        }
+        return result;
+    }
+
+    public TomcatBayeux getTomcatBayeux() {
+        return tomcatBayeux;
+    }
+
+    public String getChannel() {
+        return channel;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public String getClientId() {
+        return clientId;
+    }
+
+    public LinkedHashMap getExt() {
+        return ext;
+    }
+
+    public CometEvent getEvent() {
+        return event;
+    }
+
+    protected static void deliver(CometEvent event, ClientImpl to) throws IOException, ServletException, BayeuxException {
+        JSONArray jarray = getJSONArray(event,true);
+        if ( jarray == null ) throw new BayeuxException("No message to send!");
+        String jsonstring = jarray.toString();
+        if (log.isDebugEnabled()) {
+            log.debug("["+Thread.currentThread().getName()+"] Delivering message to[" + to + "] message:" + jsonstring);
+        }
+
+        if (to!=null) {
+            if (to.useJsonFiltered()) {
+                if (!event.getHttpServletResponse().isCommitted()) event.getHttpServletResponse().setContentType("text/json-comment-filtered");
+            }else {	
+                if (!event.getHttpServletResponse().isCommitted()) event.getHttpServletResponse().setContentType("text/json");
+            }
+        }
+
+        PrintWriter out = event.getHttpServletResponse().getWriter();
+        if (to==null) {
+            //do nothing
+        }else if ( (to.getDesirectConnType() == 0 && to.supportsLongPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_LONG_POLL) {
+            if (to.useJsonFiltered())
+                out.print("/*");
+        } else if ( (to.getDesirectConnType() == 0 && to.supportsCallbackPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_CALLBACK_POLL) {
+            String jsonp = event.getHttpServletRequest().getParameter(Bayeux.JSONP_PARAMETER);
+            if (jsonp == null)
+                jsonp = Bayeux.JSONP_DEFAULT_NAME;
+            out.print(jsonp);
+            out.print('(');
+        } else {
+            throw new BayeuxException("Client doesn't support any appropriate connection type.");
+        }
+        out.print(jsonstring);
+        if ( to == null ) {
+            //do nothing
+        } else if ( (to.getDesirectConnType() == 0 && to.supportsLongPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_LONG_POLL) {
+            if (to.useJsonFiltered())
+                out.print("*/");
+        } else if ( (to.getDesirectConnType() == 0 && to.supportsCallbackPoll()) || to.getDesirectConnType() == ClientImpl.SUPPORT_CALLBACK_POLL) {
+            out.print(");");
+        }
+        out.flush();
+        event.getHttpServletResponse().flushBuffer();
+
+
+    }
+
+    protected static JSONArray getJSONArray(CometEvent event, boolean nullok) {
+        synchronized(event) {
+            JSONArray jarray = (JSONArray) event.getHttpServletRequest().getAttribute(JSON_MSG_ARRAY);
+            if (jarray == null && (!nullok)) {
+                jarray = new JSONArray();
+                event.getHttpServletRequest().setAttribute(JSON_MSG_ARRAY, jarray);
+            }
+            return jarray;
+        }
+    }
+
+    protected JSONArray getJSONArray() {
+        return getJSONArray(event,false);
+    }
+
+    protected void addToDeliveryQueue(ClientImpl to, JSONObject msg) throws IOException, ServletException, BayeuxException {
+        synchronized (event) {
+            getJSONArray().put(msg);
+        }
+    }
+
+    protected void flushMessages(ClientImpl client) throws BayeuxException {
+        List<Message> msgs = client.takeMessages();
+        synchronized (event) {
+            try {
+                for (Iterator<Message> it = msgs.iterator(); it.hasNext(); ){
+                    MessageImpl msg = (MessageImpl)it.next();
+                    Map map = new HashMap();
+                    map.put(Bayeux.CHANNEL_FIELD,msg.getChannel().getId());
+                    if (msg.getClient()!=null) map.put(Bayeux.CLIENT_FIELD,msg.getClient().getId());
+                    map.put(Bayeux.DATA_FIELD,msg);
+                    JSONObject obj = new JSONObject(map);
+                    addToDeliveryQueue(client, obj);
+                }
+            } catch (ServletException x) {
+                throw new BayeuxException(x);
+            } catch (IOException x) {
+                throw new BayeuxException(x);
+            }
+        }
+    }
+
+    public int process(int prevops) throws BayeuxException {
+        event.getHttpServletRequest().setAttribute(CURRENT_REQ_ATTR,this);
+        return prevops;
+    }
+
+    public int getReconnectInterval() {
+        return reconnectInterval;
+    }
+
+    public String getTimeStamp() {
+        return timestampFmt.format(new Date(System.currentTimeMillis()));
+    }
+
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/RequestFactory.java b/modules/bayeux/java/org/apache/tomcat/bayeux/RequestFactory.java
new file mode 100644
index 0000000..3c4cd3f
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/RequestFactory.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import org.json.JSONObject;
+import org.apache.tomcat.bayeux.request.MetaHandshakeRequest;
+import org.apache.catalina.comet.CometEvent;
+import org.json.JSONException;
+import org.apache.tomcat.bayeux.request.MetaConnectRequest;
+import org.apache.tomcat.bayeux.request.MetaDisconnectRequest;
+import org.apache.tomcat.bayeux.request.MetaSubscribeRequest;
+import org.apache.tomcat.bayeux.request.MetaUnsubscribeRequest;
+import org.apache.tomcat.bayeux.request.PublishRequest;
+import org.apache.cometd.bayeux.Bayeux;
+
+public class RequestFactory {
+
+    public static BayeuxRequest getRequest(TomcatBayeux tomcatBayeux, CometEvent event, JSONObject msg) throws JSONException {
+        String channel = msg.optString(Bayeux.CHANNEL_FIELD);
+        if (Bayeux.META_HANDSHAKE.equals(channel)) {
+            return new MetaHandshakeRequest(tomcatBayeux,event,msg);
+        }else if (Bayeux.META_CONNECT.equals(channel)) {
+            return new MetaConnectRequest(tomcatBayeux,event,msg);
+        }else if (Bayeux.META_DISCONNECT.equals(channel)) {
+            return new MetaDisconnectRequest(tomcatBayeux,event,msg);
+        }else if (Bayeux.META_SUBSCRIBE.equals(channel)) {
+            return new MetaSubscribeRequest(tomcatBayeux,event,msg);
+        }else if (Bayeux.META_UNSUBSCRIBE.equals(channel)) {
+            return new MetaUnsubscribeRequest(tomcatBayeux,event,msg);
+        } else {
+            return new PublishRequest(tomcatBayeux,event,msg);
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/TomcatBayeux.java b/modules/bayeux/java/org/apache/tomcat/bayeux/TomcatBayeux.java
new file mode 100644
index 0000000..3ea636e
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/TomcatBayeux.java
@@ -0,0 +1,175 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux;
+
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.tribes.util.Arrays;
+import org.apache.catalina.tribes.util.UUIDGenerator;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.cometd.bayeux.Channel;
+import org.apache.cometd.bayeux.Client;
+import org.apache.cometd.bayeux.Listener;
+import org.apache.cometd.bayeux.Message;
+import org.apache.cometd.bayeux.SecurityPolicy;
+/**
+ *
+ * @version 1.0
+ */
+public class TomcatBayeux implements Bayeux {
+
+
+    protected int reconnectInterval = 5000;
+    /**
+     * a list of all active clients
+     */
+    protected HashMap<String,Client> clients = new HashMap<String,Client>();
+
+    /**
+     * a list of all active channels
+     */
+    protected LinkedHashMap<String, Channel> channels = new LinkedHashMap<String,Channel>();
+
+    /**
+     * security policy to be used.
+     */
+    protected SecurityPolicy securityPolicy = null;
+    /**
+     * default client to use when we need to send an error message but don't have a client valid reference
+     */
+    protected static ClientImpl errorClient = new ClientImpl("error-no-client",false);
+
+    /**
+     * returns the default error client
+     * @return ClientImpl
+     */
+    public static ClientImpl getErrorClient() {
+        return errorClient;
+    }
+
+    protected TomcatBayeux() {
+    }
+
+    /**
+     * should be invoked when the servlet is destroyed or when the context shuts down
+     */
+    public void destroy() {
+        throw new UnsupportedOperationException("TomcatBayeux.destroy() not yet implemented");
+    }
+
+    public Channel getChannel(String channelId, boolean create) {
+        Channel result = channels.get(channelId);
+        if (result==null && create) {
+            result = new ChannelImpl(channelId);
+            channels.put(channelId,result);
+        }
+        return result;
+    }
+
+    public Channel remove(Channel channel) {
+        return channels.remove(channel.getId());
+    }
+
+    public Client remove(Client client) {
+        if (client==null) return null;
+        for (Channel ch : getChannels()) {
+            ch.unsubscribe(client);
+        }
+        return clients.remove(client.getId());
+    }
+
+    public Client getClient(String clientId) {
+        return clients.get(clientId);
+    }
+
+    public boolean hasClient(String clientId) {
+        return clients.containsKey(clientId);
+    }
+
+    public List<Client> getClients() {
+        return java.util.Arrays.asList(clients.values().toArray(new Client[0]));
+    }
+
+    public SecurityPolicy getSecurityPolicy() {
+        return securityPolicy;
+    }
+
+    public int getReconnectInterval() {
+        return reconnectInterval;
+    }
+
+    public boolean hasChannel(String channel) {
+        return channels.containsKey(channel);
+    }
+
+    public Client newClient(String idprefix, Listener listener, boolean local, CometEvent event) {
+        String id = createUUID(idprefix);
+        ClientImpl client = new ClientImpl(id, local);
+        client.setListener(listener);
+        clients.put(id, client);
+        return client;
+    }
+
+    public Client newClient(String idprefix, Listener listener) {
+        assert listener!=null;
+        //if this method gets called, someone is using the API inside
+        //the JVM, this is a local client
+        return newClient(idprefix,listener,true, null);
+    }
+
+    protected ClientImpl getClientImpl(CometEvent event) {
+        return (ClientImpl)event.getHttpServletRequest().getAttribute(ClientImpl.COMET_EVENT_ATTR);
+    }
+
+    protected void remove(CometEvent event) {
+        ClientImpl client = getClientImpl(event);
+        if (client!=null) {
+            client.removeCometEvent(event);
+        }
+    }
+
+    public String createUUID(String idprefix) {
+        if (idprefix==null) idprefix="";
+        return idprefix + Arrays.toString(UUIDGenerator.randomUUID(false));
+    }
+
+    public List<Channel> getChannels() {
+        return java.util.Arrays.asList(channels.entrySet().toArray(new Channel[0]));
+    }
+
+    protected Message newMessage() {
+        String id = createUUID("msg-");
+        return new MessageImpl(id);
+    }
+
+    public Message newMessage(Client from) {
+        MessageImpl msg = (MessageImpl)newMessage();
+        msg.setClient(from);
+        return msg;
+    }
+    public void setSecurityPolicy(SecurityPolicy securityPolicy) {
+        this.securityPolicy = securityPolicy;
+    }
+
+    public void setReconnectInterval(int reconnectTimeout) {
+        this.reconnectInterval = reconnectTimeout;
+    }
+
+}
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java
new file mode 100644
index 0000000..9fcd397
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaConnectRequest.java
@@ -0,0 +1,124 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux.request;
+
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+import org.apache.tomcat.bayeux.BayeuxException;
+import org.apache.tomcat.bayeux.BayeuxRequest;
+import org.apache.tomcat.bayeux.ClientImpl;
+import org.apache.tomcat.bayeux.TomcatBayeux;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.tomcat.bayeux.*;
+
+/******************************************************************************
+ * Handshake request Bayeux message.
+ *
+ * @author Guy A. Molinari
+ * @version 1.0
+ *
+ */
+public class MetaConnectRequest extends RequestBase implements BayeuxRequest {
+    protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>();
+
+    static {
+        responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_CONNECT);
+        responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE);
+        responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>());
+    }
+
+    public MetaConnectRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
+        super(tb, event, jsReq);
+        if (clientId!=null && getTomcatBayeux().hasClient(clientId)) {
+            event.getHttpServletRequest().setAttribute("client",getTomcatBayeux().getClient(clientId));
+        }
+    }
+
+
+    /**
+     * Check client request for validity.
+     *
+     * Per section 4.2.1 of the Bayuex spec a connect request must contain:
+     *  1) The "/meta/connect" channel identifier.
+     *  2) The clientId returned by the server after handshake.
+     *  3) The desired connectionType (must be one of the server's supported
+     *     types returned by handshake response.
+     *
+     * @return HttpError This method returns null if no errors were found
+     */
+    public HttpError validate() {
+        if(clientId==null|| (!getTomcatBayeux().hasClient(clientId)))
+            return new HttpError(400,"Client Id not valid.", null);
+        if (! (Bayeux.TRANSPORT_LONG_POLL.equals(conType) || Bayeux.TRANSPORT_CALLBACK_POLL.equals(conType)))
+            return new HttpError(400,"Unsupported connection type.",null);
+        return null;//no error
+    }
+
+    /**
+     * Transition to connected state, flushing pending messages if
+     * available.  If there are pending subscriptions and no messages to
+     * flush then the connection is held until there is a pending publish
+     * event to be delivered to this client (Section 4.2.2 of spec).
+     */
+    public int process(int prevops) throws BayeuxException {
+        super.process(prevops);
+        response = (HashMap<String, Object>)responseTemplate.clone();
+        ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId);
+        boolean success = false;
+        HttpError error = validate();
+        if (error == null) {
+            client.setDesirectConnType(desiredConnTypeFlag);
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.RETRY_RESPONSE);
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.INTERVAL_FIELD, getReconnectInterval());
+            success = true;
+        }else {
+            response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
+            response.put(Bayeux.ERROR_FIELD, error.toString());
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.HANDSHAKE_RESPONSE);
+            if (client==null) client = TomcatBayeux.getErrorClient();
+        }
+        response.put(Bayeux.CLIENT_FIELD, client.getId());
+        response.put(Bayeux.TIMESTAMP_FIELD,getTimeStamp());
+        try {
+            JSONObject obj = new JSONObject(response);
+            addToDeliveryQueue(client, obj);
+        } catch (ServletException x) {
+            throw new BayeuxException(x);
+        } catch (IOException x) {
+            throw new BayeuxException(x);
+        }
+
+        //return immediately if there is no subscriptions
+        //so that we can process the next message
+        int result = client.isSubscribed()?1:0;
+
+        if (success && client!=null && client.hasMessages()) {
+            //send out messages
+            flushMessages(client);
+            result = 0; //flush out the messages
+        }
+
+        return result;
+    }
+}
+
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java
new file mode 100644
index 0000000..3a7b76e
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaDisconnectRequest.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux.request;
+
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+import org.apache.tomcat.bayeux.BayeuxException;
+import org.apache.tomcat.bayeux.BayeuxRequest;
+import org.apache.tomcat.bayeux.ClientImpl;
+import org.apache.tomcat.bayeux.TomcatBayeux;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.tomcat.bayeux.*;
+import org.apache.cometd.bayeux.Channel;
+
+/******************************************************************************
+ * Handshake request Bayeux message.
+ *
+ * @author Guy A. Molinari
+ * @version 1.0
+ *
+ */
+public class MetaDisconnectRequest extends RequestBase implements BayeuxRequest {
+
+    protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>();
+
+    static {
+        responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_DISCONNECT);
+        responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE);
+        responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>());
+    }
+
+    public MetaDisconnectRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
+        super(tb, event, jsReq);
+    }
+
+
+    /**
+     * Check client request for validity.
+     *
+     * Per section 4.4.1 of the Bayuex spec a connect request must contain:
+     *  1) The "/meta/disconnect" channel identifier.
+     *  2) The clientId.
+     *
+     * @return HttpError This method returns null if no errors were found
+     */
+    public HttpError validate() {
+        if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId)))
+            return new HttpError(400,"Client Id not valid.", null);
+//        if (! (Bayeux.TRANSPORT_LONG_POLL.equals(conType) || Bayeux.TRANSPORT_CALLBACK_POLL.equals(conType)))
+//            return new HttpError(400,"Unsupported connection type.",null);
+        return null;//no error
+    }
+
+    /**
+     * Disconnect a client session.
+     */
+    public int process(int prevops) throws BayeuxException {
+        super.process(prevops);
+        response = (HashMap<String, Object>)responseTemplate.clone();
+        ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId);
+        HttpError error = validate();
+        if (error == null) {
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "retry");
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("interval", getReconnectInterval());
+        }else {
+            getTomcatBayeux().remove(client);
+            response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
+            response.put(Bayeux.ERROR_FIELD, error.toString());
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "none");
+            if (client==null) client = TomcatBayeux.getErrorClient();
+        }
+        response.put(Bayeux.CLIENT_FIELD, client.getId());
+        try {
+            JSONObject obj = new JSONObject(response);
+            addToDeliveryQueue(client, obj);
+        } catch (ServletException x) {
+            throw new BayeuxException(x);
+        } catch (IOException x) {
+            throw new BayeuxException(x);
+        }
+        return 0;
+    }
+}
+
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java
new file mode 100644
index 0000000..5ce2566
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaHandshakeRequest.java
@@ -0,0 +1,115 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux.request;
+
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+import org.apache.tomcat.bayeux.BayeuxException;
+import org.apache.tomcat.bayeux.BayeuxRequest;
+import org.apache.tomcat.bayeux.ClientImpl;
+import org.apache.tomcat.bayeux.TomcatBayeux;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.tomcat.bayeux.*;
+
+/******************************************************************************
+ * Handshake request Bayeux message.
+ *
+ * @author Guy A. Molinari
+ * @version 1.0
+ *
+ */
+public class MetaHandshakeRequest extends RequestBase implements BayeuxRequest {
+
+    protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>();
+
+    static {
+        responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_HANDSHAKE);
+        responseTemplate.put(Bayeux.VERSION_FIELD,"1.0");
+        responseTemplate.put(Bayeux.SUPP_CONNECTION_TYPE_FIELD,new String[] { Bayeux.TRANSPORT_LONG_POLL, Bayeux.TRANSPORT_CALLBACK_POLL });
+        responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE);
+        responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>());
+    }
+
+    public MetaHandshakeRequest(TomcatBayeux tomcatBayeux, CometEvent event, JSONObject jsReq) throws JSONException {
+        super(tomcatBayeux, event, jsReq);
+    }
+
+
+    public String getVersion() { return version; }
+    public String getMinimumVersion() { return minVersion; }
+
+
+    /**
+     * Check client request for validity.
+     *
+     * Per section 4.1.1 of the Bayuex spec a handshake request must contain:
+     *  1) The "/meta/handshake" channel identifier.
+     *  2) The version of the protocol supported by the client
+     *  3) The client's supported connection types.
+     *
+     * @return HttpError This method returns null if no errors were found
+     */
+    public HttpError validate() {
+        boolean error = (version==null || version.length()==0);
+        if (!error) error = suppConnTypesFlag==0;
+        if (error) return new HttpError(400,"Invalid handshake request, supportedConnectionType field missing.",null);
+        else return null;
+    }
+
+    /**
+     * Generate and return a client identifier.  Return a list of
+     * supported connection types.  Must be a subset of or identical to
+     * the list of types supported by the client.  See section 4.1.2 of
+     * the Bayuex specification.
+     */
+    public int process(int prevops) throws BayeuxException {
+        super.process(prevops);
+        response = (HashMap<String, Object>)responseTemplate.clone();
+        ClientImpl client = null;
+        HttpError error = validate();
+        if (error == null) {
+            client = (ClientImpl) getTomcatBayeux().newClient("http-", null, false,getEvent());
+            clientId = client.getId();
+            client.setSupportedConnTypes(suppConnTypesFlag);
+            client.setUseJsonFiltered(getExt().get(Bayeux.JSON_COMMENT_FILTERED_FIELD) != null);
+            response.put(Bayeux.CLIENT_FIELD, client.getId());
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.RETRY_RESPONSE);
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.INTERVAL_FIELD, getReconnectInterval());
+        }else {
+            response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
+            response.put(Bayeux.ERROR_FIELD, error.toString());
+            client = TomcatBayeux.getErrorClient();
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.NONE_RESPONSE);
+        }
+        try {
+            JSONObject obj = new JSONObject(response);
+            addToDeliveryQueue(client, obj);
+        } catch (ServletException x) {
+            throw new BayeuxException(x);
+        } catch (IOException x) {
+            throw new BayeuxException(x);
+        }
+        return 0;
+    }
+}
+
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java
new file mode 100644
index 0000000..b37a46f
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaSubscribeRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux.request;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+import org.apache.tomcat.bayeux.BayeuxException;
+import org.apache.tomcat.bayeux.BayeuxRequest;
+import org.apache.tomcat.bayeux.ChannelImpl;
+import org.apache.tomcat.bayeux.ClientImpl;
+import org.apache.tomcat.bayeux.TomcatBayeux;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Channel;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.tomcat.bayeux.*;
+
+/******************************************************************************
+ * Handshake request Bayeux message.
+ *
+ * @author Guy A. Molinari
+ * @version 1.0
+ */
+public class MetaSubscribeRequest extends RequestBase implements BayeuxRequest {
+
+    protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>();
+
+    static {
+        responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_SUBSCRIBE);
+        responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE);
+        responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>());
+    }
+
+    public MetaSubscribeRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
+        super(tb, event, jsReq);
+    }
+
+
+    /**
+     * Check client request for validity.
+     *
+     * Per section 4.5.1 of the Bayuex spec a connect request must contain:
+     *  1) The "/meta/subscribe" channel identifier.
+     *  2) The clientId.
+     *  3) The subscription.  This is the name of the channel of interest,
+     *     or a pattern.
+     *
+     * @return HttpError This method returns null if no errors were found
+     */
+    public HttpError validate() {
+        if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId)))
+            return new HttpError(400,"Client Id not valid.", null);
+        if (subscription==null||subscription.length()==0)
+            return new HttpError(400,"Subscription missing.",null);
+        return null;//no error
+    }
+
+    /**
+     * Register interest for one or more channels.  Per section 2.2.1 of the
+     * Bayeux spec, a pattern may be specified.  Assign client to matching
+     * channels and inverse client to channel reference.
+     */
+    public int process(int prevops) throws BayeuxException {
+        super.process(prevops);
+        response = (HashMap<String, Object>)this.responseTemplate.clone();
+        ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId);
+        HttpError error = validate();
+        if (error == null) {
+            boolean wildcard = subscription.indexOf('*')!=-1;
+            boolean subscribed = false;
+            if (wildcard) {
+                List<Channel> channels = getTomcatBayeux().getChannels();
+                Iterator<Channel> it = channels.iterator();
+                while (it.hasNext()) {
+                    ChannelImpl ch = (ChannelImpl)it.next();
+                    if (ch.matches(subscription)) {
+                        ch.subscribe(client);
+                        subscribed = true;
+                    }
+                }
+            }else {
+                ChannelImpl ch = (ChannelImpl)getTomcatBayeux().getChannel(subscription,true);
+                ch.subscribe(client);
+                subscribed = true;
+            }
+            response.put(Bayeux.SUCCESSFUL_FIELD, Boolean.valueOf(subscribed));
+            response.put(Bayeux.SUBSCRIPTION_FIELD,subscription);
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "retry");
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("interval", getReconnectInterval());
+        }else {
+            response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
+            response.put(Bayeux.ERROR_FIELD, error.toString());
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "handshake");
+            if (client==null) client = TomcatBayeux.getErrorClient();
+        }
+        response.put(Bayeux.CLIENT_FIELD, client.getId());
+        response.put(Bayeux.TIMESTAMP_FIELD,getTimeStamp());
+        try {
+            JSONObject obj = new JSONObject(response);
+            addToDeliveryQueue(client, obj);
+        } catch (ServletException x) {
+            throw new BayeuxException(x);
+        } catch (IOException x) {
+            throw new BayeuxException(x);
+        }
+        return 0;
+    }
+}
+
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java
new file mode 100644
index 0000000..38401ed
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/request/MetaUnsubscribeRequest.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux.request;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+import org.apache.tomcat.bayeux.BayeuxException;
+import org.apache.tomcat.bayeux.BayeuxRequest;
+import org.apache.tomcat.bayeux.ChannelImpl;
+import org.apache.tomcat.bayeux.ClientImpl;
+import org.apache.tomcat.bayeux.TomcatBayeux;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Channel;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.tomcat.bayeux.*;
+
+/******************************************************************************
+ * Handshake request Bayeux message.
+ *
+ * @author Guy A. Molinari
+ * @version 1.0
+ *
+ */
+public class MetaUnsubscribeRequest extends RequestBase implements BayeuxRequest {
+
+    protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>();
+
+    static {
+        responseTemplate.put(Bayeux.CHANNEL_FIELD,Bayeux.META_UNSUBSCRIBE);
+        responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE);
+        responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>());
+    }
+
+    public MetaUnsubscribeRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
+        super(tb, event, jsReq);
+    }
+
+
+    /**
+     * Check client request for validity.
+     *
+     * Per section 4.6.1 of the Bayuex spec a connect request must contain:
+     *  1) The "/meta/unsubscribe" channel identifier.
+     *  2) The clientId.
+     *  3) The subscription.  This is the name of the channel of interest,
+     *     or a pattern.
+     *
+     * @return HttpError This method returns null if no errors were found
+     */
+    public HttpError validate() {
+        if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId)))
+            return new HttpError(400,"Client Id not valid.", null);
+        if (subscription==null||subscription.length()==0)
+            return new HttpError(400,"Subscription missing.",null);
+        return null;//no error
+    }
+
+    /**
+     * De-register interest for one or more channels.  Per section 2.2.1 of the
+     * Bayeux spec, a pattern may be specified.  Sever relationships.
+     */
+    public int process(int prevops) throws BayeuxException {
+        super.process(prevops);
+        response = (HashMap<String, Object>)responseTemplate.clone();
+        ClientImpl client = (ClientImpl)getTomcatBayeux().getClient(clientId);
+        HttpError error = validate();
+        if (error == null) {
+            boolean wildcard = subscription.indexOf('*')!=-1;
+            boolean unsubscribed = false;
+            if (wildcard) {
+                List<Channel> channels = getTomcatBayeux().getChannels();
+                Iterator<Channel> it = channels.iterator();
+                while (it.hasNext()) {
+                    ChannelImpl ch = (ChannelImpl)it.next();
+                    if (ch.matches(subscription)) {
+                        ch.unsubscribe(client);
+                        unsubscribed = true;
+                    }
+                }
+            }else {
+                ChannelImpl ch = (ChannelImpl)getTomcatBayeux().getChannel(subscription,true);
+                ch.unsubscribe(client);
+                unsubscribed = true;
+            }
+            response.put(Bayeux.SUCCESSFUL_FIELD, Boolean.valueOf(unsubscribed));
+            response.put(Bayeux.SUBSCRIPTION_FIELD,subscription);
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "retry");
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("interval", getReconnectInterval());
+        }else {
+            response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
+            response.put(Bayeux.ERROR_FIELD, error.toString());
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put("reconnect", "handshake");
+            if (client==null) client = TomcatBayeux.getErrorClient();
+        }
+        response.put(Bayeux.CLIENT_FIELD, client.getId());
+        response.put(Bayeux.TIMESTAMP_FIELD,getTimeStamp());
+        try {
+            JSONObject obj = new JSONObject(response);
+            addToDeliveryQueue(client, obj);
+        } catch (ServletException x) {
+            throw new BayeuxException(x);
+        } catch (IOException x) {
+            throw new BayeuxException(x);
+        }
+        return 0;
+    }
+}
+
diff --git a/modules/bayeux/java/org/apache/tomcat/bayeux/request/PublishRequest.java b/modules/bayeux/java/org/apache/tomcat/bayeux/request/PublishRequest.java
new file mode 100644
index 0000000..afb5343
--- /dev/null
+++ b/modules/bayeux/java/org/apache/tomcat/bayeux/request/PublishRequest.java
@@ -0,0 +1,141 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.bayeux.request;
+
+import java.io.IOException;
+import java.util.HashMap;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.tomcat.bayeux.HttpError;
+import org.apache.tomcat.bayeux.BayeuxException;
+import org.apache.tomcat.bayeux.ChannelImpl;
+import org.apache.tomcat.bayeux.ClientImpl;
+import org.apache.tomcat.bayeux.MessageImpl;
+import org.apache.tomcat.bayeux.RequestBase;
+import org.apache.tomcat.bayeux.TomcatBayeux;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.apache.cometd.bayeux.Bayeux;
+import org.apache.juli.logging.Log;
+import org.apache.juli.logging.LogFactory;
+
+/******************************************************************************
+ * Handshake request Bayeux message.
+ *
+ * @author Guy A. Molinari
+ * @version 1.0
+ *
+ */
+public class PublishRequest extends RequestBase {
+
+    private static final Log log = LogFactory.getLog(PublishRequest.class);
+
+    protected static HashMap<String,Object> responseTemplate = new HashMap<String,Object>();
+
+    static {
+        responseTemplate.put(Bayeux.SUCCESSFUL_FIELD,Boolean.TRUE);
+        responseTemplate.put(Bayeux.ADVICE_FIELD, new HashMap<String, Object>());
+    }
+
+    JSONObject msgData = null;
+
+    public PublishRequest(TomcatBayeux tb, CometEvent event, JSONObject jsReq) throws JSONException {
+        super(tb, event, jsReq);
+    }
+
+
+    /**
+     * Check client request for validity.
+     *
+     * Per section 5.1.1 of the Bayuex spec a connect request must contain:
+     *  1) The channel identifier of the channel for publication.
+     *  2) The data to send.
+     *
+     * @return HttpError This method returns null if no errors were found
+     */
+    @Override
+    public HttpError validate() {
+        if(channel==null|| (!this.getTomcatBayeux().hasChannel(channel)))
+            return new HttpError(400,"Channel Id not valid.", null);
+        if(data==null || data.length()==0)
+            return new HttpError(400,"Message data missing.", null);
+        try {
+            this.msgData = new JSONObject(data);
+        }catch (JSONException x) {
+            return new HttpError(400,"Invalid JSON object in data attribute.",x);
+        }
+        if(clientId==null|| (!this.getTomcatBayeux().hasClient(clientId)))
+            return new HttpError(400,"Client Id not valid.", null);
+        return null;//no error
+    }
+
+    /**
+     *  Send the event message to all registered subscribers.
+     */
+    @Override
+    public int process(int prevops) throws BayeuxException {
+        super.process(prevops);
+        response = (HashMap<String, Object>)responseTemplate.clone();
+        ClientImpl client = clientId!=null?(ClientImpl)getTomcatBayeux().getClient(clientId):
+                                           (ClientImpl)event.getHttpServletRequest().getAttribute("client");
+        boolean success = false;
+        HttpError error = validate();
+        if (error == null) {
+            ChannelImpl chimpl = (ChannelImpl)getTomcatBayeux().getChannel(channel,false);
+            MessageImpl mimpl = (MessageImpl)getTomcatBayeux().newMessage(client);
+
+            try {
+                String[] keys = JSONObject.getNames(msgData);
+                for (int i = 0; i < keys.length; i++) {
+                    mimpl.put(keys[i], msgData.get(keys[i]));
+                }
+                success = true;
+                ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.RETRY_RESPONSE);
+                ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.INTERVAL_FIELD, getReconnectInterval());
+            }catch (JSONException x) {
+                if (log.isErrorEnabled()) log.error("Unable to parse:"+msgData,x);
+                throw new BayeuxException(x);
+            }
+            chimpl.publish(mimpl);
+        }
+        if(!success) {
+            response.put(Bayeux.SUCCESSFUL_FIELD,Boolean.FALSE);
+            response.put(Bayeux.ERROR_FIELD, error.toString());
+            ((HashMap) response.get(Bayeux.ADVICE_FIELD)).put(Bayeux.RECONNECT_FIELD, Bayeux.HANDSHAKE_RESPONSE);
+            if (client==null) client = TomcatBayeux.getErrorClient();
+        }
+        response.put(Bayeux.CHANNEL_FIELD,channel);
+        response.put(Bayeux.CLIENT_FIELD, client.getId());
+        try {
+            JSONObject obj = new JSONObject(response);
+            addToDeliveryQueue(client, obj);
+        } catch (ServletException x) {
+            throw new BayeuxException(x);
+        } catch (IOException x) {
+            throw new BayeuxException(x);
+        }
+
+        if (success && client!=null && client.hasMessages()) {
+            //send out messages
+            flushMessages(client);
+        }
+
+        return 0;
+    }
+}
+
diff --git a/modules/bayeux/test/org/apache/cometd/bayeux/samples/BayeuxStockTicker.java b/modules/bayeux/test/org/apache/cometd/bayeux/samples/BayeuxStockTicker.java
new file mode 100644
index 0000000..cb8d30c
--- /dev/null
+++ b/modules/bayeux/test/org/apache/cometd/bayeux/samples/BayeuxStockTicker.java
@@ -0,0 +1,232 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.cometd.bayeux.samples;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextAttributeEvent;
+import org.apache.cometd.bayeux.Bayeux;
+
+import java.text.DecimalFormat;
+import java.util.List;
+import java.util.Random;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.cometd.bayeux.Client;
+import org.apache.cometd.bayeux.Listener;
+import org.apache.cometd.bayeux.Message;
+import org.apache.cometd.bayeux.Channel;
+
+public class BayeuxStockTicker implements ServletContextListener,
+        ServletContextAttributeListener, Listener {
+
+    static AtomicInteger counter = new AtomicInteger(0);
+    protected int id;
+    protected Bayeux b;
+    protected Client c;
+    protected boolean alive = true;
+    protected boolean initialized = false;
+    protected TickerThread tt = new TickerThread();
+
+    public BayeuxStockTicker() {
+        id = counter.incrementAndGet();
+        System.out.println("new listener created with id:" + id);
+    }
+
+    public void contextDestroyed(ServletContextEvent servletContextEvent) {
+        alive = false;
+        tt.run = false;
+        tt.interrupt();
+    }
+
+    public void contextInitialized(ServletContextEvent servletContextEvent) {
+    }
+
+    public void attributeAdded(ServletContextAttributeEvent scae) {
+        if (scae.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX)) {
+            if (initialized) return;
+            initialized = true;
+            System.out.println("Starting stock ticker server client!");
+            b = (Bayeux) scae.getValue();
+            c = b.newClient("stock-ticker-", this);
+            tt.start();
+        }
+    }
+
+    public void attributeRemoved(ServletContextAttributeEvent scae) {
+        if (scae.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX)) {
+            initialized = false;
+            b = (Bayeux) scae.getValue();
+            List<Channel> chs = b.getChannels();
+            for (Channel ch : chs) {
+                ch.unsubscribe(c);
+            }
+        }
+    }
+
+    public void attributeReplaced(
+            ServletContextAttributeEvent servletContextAttributeEvent) {
+    }
+
+    public void removed(boolean timeout) {
+        System.out.println("Client removed.");
+    }
+
+    public void deliver(Message[] msgs) {
+        for (int i = 0; msgs != null && i < msgs.length; i++) {
+            Message msg = msgs[i];
+            System.out.println("[stock ticker server client ]received message:" + msg);
+        }
+    }
+
+    public class TickerThread extends Thread {
+        public boolean run = true;
+
+        public TickerThread() {
+            setName("Ticker Thread");
+        }
+
+        public void run() {
+            try {
+
+                Stock[] stocks = new Stock[] {
+                        new Stock("GOOG", 435.43),
+                        new Stock("YHOO", 27.88),
+                        new Stock("ASF", 1015.55), };
+                for (Stock s : stocks) {
+                    Channel ch = b.getChannel("/stock/"+s.getSymbol(), true);
+                    ch.subscribe(c);
+
+                }
+                Random r = new Random(System.currentTimeMillis());
+                while (run) {
+                    for (int j = 0; j < 1; j++) {
+                        int i = r.nextInt() % 3;
+                        if (i < 0)
+                            i = i * (-1);
+                        Stock stock = stocks[i];
+                        double change = r.nextDouble();
+                        boolean plus = r.nextBoolean();
+                        if (plus) {
+                            stock.setValue(stock.getValue() + change);
+                        } else {
+                            stock.setValue(stock.getValue() - change);
+                        }
+                        Channel ch = b.getChannel("/stock/"+stock.getSymbol(), true);
+                        Message m = b.newMessage(c);
+                        m.put("stock", stock.toString());
+                        m.put("symbol", stock.getSymbol());
+                        m.put("price", stock.getValueAsString());
+                        m.put("change", stock.getLastChangeAsString());
+                        ch.publish(m);
+                        System.out.println("Bayeux Stock: "+stock.getSymbol()+" Price: "+stock.getValueAsString()+" Change: "+stock.getLastChangeAsString());
+                    }
+                    Thread.sleep(850);
+                }
+            } catch (InterruptedException ix) {
+
+            } catch (Exception x) {
+                x.printStackTrace();
+            }
+        }
+    }
+
+    public static class Stock {
+        protected static DecimalFormat df = new DecimalFormat("0.00");
+        protected String symbol = "";
+        protected double value = 0.0d;
+        protected double lastchange = 0.0d;
+        protected int cnt = 0;
+
+        public Stock(String symbol, double initvalue) {
+            this.symbol = symbol;
+            this.value = initvalue;
+        }
+
+        public void setCnt(int c) {
+            this.cnt = c;
+        }
+
+        public int getCnt() {
+            return cnt;
+        }
+
+        public String getSymbol() {
+            return symbol;
+        }
+
+        public double getValue() {
+            return value;
+        }
+
+        public void setValue(double value) {
+            double old = this.value;
+            this.value = value;
+            this.lastchange = value - old;
+        }
+
+        public String getValueAsString() {
+            return df.format(value);
+        }
+
+        public double getLastChange() {
+            return this.lastchange;
+        }
+
+        public void setLastChange(double lastchange) {
+            this.lastchange = lastchange;
+        }
+
+        public String getLastChangeAsString() {
+            return df.format(lastchange);
+        }
+
+        public int hashCode() {
+            return symbol.hashCode();
+        }
+
+        public boolean equals(Object other) {
+            if (other instanceof Stock) {
+                return this.symbol.equals(((Stock) other).symbol);
+            } else {
+                return false;
+            }
+        }
+
+        public String toString(){
+            StringBuilder buf = new StringBuilder("STOCK#");
+            buf.append(getSymbol());
+            buf.append("#");
+            buf.append(getValueAsString());
+            buf.append("#");
+            buf.append(getLastChangeAsString());
+            buf.append("#");
+            buf.append(String.valueOf(getCnt()));
+            return buf.toString();
+
+        }
+
+        public Object clone() {
+            Stock s = new Stock(this.getSymbol(), this.getValue());
+            s.setLastChange(this.getLastChange());
+            s.setCnt(this.cnt);
+            return s;
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/modules/bayeux/test/org/apache/cometd/bayeux/samples/EchoChatClient.java b/modules/bayeux/test/org/apache/cometd/bayeux/samples/EchoChatClient.java
new file mode 100644
index 0000000..5f1d458
--- /dev/null
+++ b/modules/bayeux/test/org/apache/cometd/bayeux/samples/EchoChatClient.java
@@ -0,0 +1,118 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.cometd.bayeux.samples;
+
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletContextAttributeListener;
+import javax.servlet.ServletContextAttributeEvent;
+import org.apache.cometd.bayeux.Bayeux;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.cometd.bayeux.Client;
+import org.apache.cometd.bayeux.Listener;
+import org.apache.cometd.bayeux.Message;
+import org.apache.cometd.bayeux.Channel;
+
+public class EchoChatClient implements ServletContextListener, ServletContextAttributeListener, Listener {
+
+    static AtomicInteger counter = new AtomicInteger(0);
+    protected int id;
+    protected Bayeux b;
+    protected Client c;
+    protected boolean alive = true;
+    protected TimestampThread tt = new TimestampThread();
+
+    public EchoChatClient() {
+        id = counter.incrementAndGet();
+        System.out.println("new listener created with id:"+id);
+    }
+
+    public void contextDestroyed(ServletContextEvent servletContextEvent) {
+        alive = false;
+        tt.interrupt();
+    }
+
+    public void contextInitialized(ServletContextEvent servletContextEvent) {
+    }
+
+    public void attributeAdded(ServletContextAttributeEvent scae) {
+        if (scae.getName().equals(Bayeux.DOJOX_COMETD_BAYEUX)) {
+            System.out.println("Starting echo chat client!");
+            b = (Bayeux)scae.getValue();
+            c = b.newClient("echochat-",this);
+            Channel ch = b.getChannel("/chat/demo",true);
+            ch.subscribe(c);
+            tt.start();
+        }
+    }
+
+    public void attributeRemoved(ServletContextAttributeEvent servletContextAttributeEvent) {
+    }
+
+    public void attributeReplaced(ServletContextAttributeEvent servletContextAttributeEvent) {
+    }
+
+    public void removed(boolean timeout) {
+        System.out.println("Client removed.");
+    }
+
+    public void deliver(Message[] msgs) {
+        for (int i=0; msgs!=null && i<msgs.length; i++) {
+            Message msg = msgs[i];
+            System.out.println("[echochatclient ]received message:" + msg);
+            Message m = b.newMessage(c);
+            m.putAll(msg);
+            //echo the same message
+            m.put("user", "echochatserver");
+            if (m.containsKey("msg")) {
+                //simple chat demo
+                String chat = (String) m.get("msg");
+                m.put("msg", "echochatserver|I received your message-" + chat.substring(chat.indexOf("|") + 1));
+            }
+            System.out.println("[echochatclient ]sending message:" + m);
+            msg.getChannel().publish(m);
+        }
+    }
+
+    public class TimestampThread extends Thread {
+        public TimestampThread() {
+            setDaemon(true);
+        }
+
+        public void run() {
+            while (alive) {
+                try {
+                    sleep(5000);
+                    Channel ch = b.getChannel("/chat/demo",false);
+                    if (ch.getSubscribers().size()<=1) {
+                        continue;
+                    }
+                    Message m = b.newMessage(c);
+                    m.put("user","echochatserver");
+                    m.put("chat","Time is:"+new java.sql.Date(System.currentTimeMillis()).toLocaleString());
+                    m.put("join",false);
+                    ch.publish(m);
+                }catch (InterruptedException ignore) {
+                    Thread.currentThread().interrupted();
+                }catch (Exception x) {
+                    x.printStackTrace();
+                }
+            }
+        }
+    }
+}
diff --git a/modules/bayeux/webapps/cometd/WEB-INF/web.xml b/modules/bayeux/webapps/cometd/WEB-INF/web.xml
new file mode 100644
index 0000000..ea3d943
--- /dev/null
+++ b/modules/bayeux/webapps/cometd/WEB-INF/web.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<web-app
+   xmlns="http://java.sun.com/xml/ns/javaee"
+   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+   xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
+   version="2.5">
+  <display-name>Cometd Test WebApp</display-name>
+
+  <servlet>
+    <servlet-name>cometd</servlet-name>
+    <servlet-class>org.apache.tomcat.bayeux.BayeuxServlet</servlet-class>
+    <init-param>
+      <param-name>timeout</param-name>
+      <param-value>120000000</param-value>
+    </init-param>
+    <init-param>
+      <param-name>reconnectInterval</param-name>
+      <param-value>250</param-value>
+    </init-param>
+    <load-on-startup>1</load-on-startup>
+  </servlet>
+
+  <servlet-mapping>
+    <servlet-name>cometd</servlet-name>
+    <url-pattern>/cometd/*</url-pattern>
+  </servlet-mapping>
+
+  <listener>
+    <listener-class>org.apache.cometd.bayeux.samples.EchoChatClient</listener-class>
+  </listener>
+  <listener>
+    <listener-class>org.apache.cometd.bayeux.samples.BayeuxStockTicker</listener-class>
+  </listener>
+
+</web-app>
+
+
diff --git a/modules/bayeux/webapps/cometd/examples/simplechat/cometdchat.htm b/modules/bayeux/webapps/cometd/examples/simplechat/cometdchat.htm
new file mode 100644
index 0000000..0f30cad
--- /dev/null
+++ b/modules/bayeux/webapps/cometd/examples/simplechat/cometdchat.htm
@@ -0,0 +1,130 @@
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<html>
+<header><title>Comet Simple Chat Application</title>
+
+<script type="text/javascript" src="../../dojo/dojo.js.uncompressed.js"></script>
+<script type="text/javascript" src="../../dojox/cometd.js"></script>
+<script type="text/javascript" src="../../dojox/cometd/_base.js"></script>
+
+<script type="text/javascript">
+
+dojo.require("dojox.cometd");
+
+dojo.addOnLoad(function() {
+  dojo.byId("message").style.visibility='hidden';
+  dojox.cometd.init("/cometd/cometd");
+});
+
+
+function trim(str) {
+    return str.replace(/(^\s+|\s+$)/g,'');
+}
+
+
+function clear() {
+  dojo.byId("msgtext").value = "";
+  dojo.byId("msgtext").focus();
+}
+
+
+function enterKeyHandler(e) {
+if (!e) e = window.event;
+   if (e.keyCode == 13) {
+      send(trim(dojo.byId("msgtext").value));
+      clear();
+   }
+
+}
+
+
+function connect() {
+  dojox.cometd.subscribe("/chat/demo", onMsgEvent);
+  dojo.byId("login").style.visibility='hidden';
+  dojo.byId("message").style.visibility='visible';
+  send("Has joined the chat room");
+  clear();
+  dojo.byId("msgtext").onkeydown = enterKeyHandler;
+  dojo.byId("myname").appendChild(document.createTextNode("-> " + dojo.byId("scrname").value + " <-"));
+}
+
+
+function onMsgEvent(event) {
+
+   // Break apart the text string into screen name and message parts.
+   var str = trim(event.data.msg);
+   var scrname = ""; 
+   if (str.match(/^.*[|].*$/)) {
+       var spl = str.split("|");
+       scrname = spl[0];
+       str = " - " + spl[1];
+   }
+
+   // Insert the screen name in red and the message black into the DOM
+   var newP = document.createElement("p");
+   var fnt1 = document.createElement("font");
+   var attr1 = document.createAttribute("color");
+   attr1.nodeValue = "red";
+   fnt1.setAttributeNode(attr1);
+
+   var newT = document.createTextNode(scrname);
+   fnt1.appendChild(newT);
+
+   newP.appendChild(fnt1);  
+
+   var fnt2 = document.createElement("font");
+   var attr2 = document.createAttribute("color");
+   attr2.nodeValue = "black";
+   fnt2.setAttributeNode(attr2);
+
+   var newT2 = document.createTextNode(str);
+   fnt2.appendChild(newT2);
+
+   newP.appendChild(fnt2);  
+
+   dojo.byId("dialog").appendChild(newP)
+}
+
+
+function send(msg) {
+  var scrname = dojo.byId("scrname").value;
+  var evt = {'data': { 'msg': trim(scrname) + '|' + msg }};
+  onMsgEvent(evt);  // Echo local
+  dojox.cometd.publish("/chat/demo", evt.data);
+}
+
+
+</script>
+
+</head>
+</header>
+<body>
+<form>
+<div id="login">
+Screen name:&nbsp;<input type="text" id="scrname">
+<input type=Button Id=logbtn value=Connect onClick=connect()><br/>Type a screen name and click the 'Connect' button
+</div>
+
+<div id="dialog"></div>
+<hr/>
+<div id="message">
+<div id="myname"></div> Is my screen name<br/>
+<textarea rows="3" cols="50" id="msgtext"></textarea>[ENTER] sends message</div>
+</form>
+</body>
+</html>
+
diff --git a/modules/bayeux/webapps/cometd/examples/simplechat/ticker.html b/modules/bayeux/webapps/cometd/examples/simplechat/ticker.html
new file mode 100644
index 0000000..51e4e6b
--- /dev/null
+++ b/modules/bayeux/webapps/cometd/examples/simplechat/ticker.html
@@ -0,0 +1,143 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 3.2//EN">
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+-->
+<html>
+<head>
+<meta http-equiv="Content-Type" content="text/html;charset=ISO-8859-1" >
+<title>Bayeux Stock Ticker</title>
+<script type="text/javascript" src="../../dojo/dojo.js.uncompressed.js"></script>
+<script type="text/javascript" src="../../dojox/cometd.js"></script>
+<script type="text/javascript" src="../../dojox/cometd/_base.js"></script>
+<script type="text/javascript">
+
+dojo.require("dojox.cometd");
+
+dojo.addOnLoad(function() {
+  dojox.cometd.init("/cometd/cometd");
+  dojox.cometd.startBatch();
+  dojox.cometd.subscribe("/stock/GOOG", onMsgEvent);
+  dojox.cometd.subscribe("/stock/YHOO", onMsgEvent);
+  dojox.cometd.subscribe("/stock/SPRG", onMsgEvent);
+  dojox.cometd.endBatch();
+});
+
+
+dojo.addOnUnload(function() {
+	  dojox.cometd.startBatch();
+	  dojox.cometd.unsubscribe("/stock/GOOG", this,"");
+	  dojox.cometd.unsubscribe("/stock/YHOO", this,"");
+	  dojox.cometd.unsubscribe("/stock/SPRG", this,"");
+	  dojox.cometd.endBatch();
+	});
+
+
+function subscribe(box, symbol) {
+	if (box.checked) {
+		dojox.cometd.subscribe("/stock/"+symbol, onMsgEvent);
+		var rowCurrent = dojo.byId("row."+symbol);
+		rowCurrent.bgColor="white";
+	} else {
+		dojox.cometd.unsubscribe("/stock/"+symbol, onMsgEvent);
+		var rowCurrent = dojo.byId("row."+symbol);
+		rowCurrent.bgColor="gray";
+	}
+}
+
+function removeChildrenFromNode(node)
+{
+   if(node == undefined || node == null)
+   {
+      return;
+   }
+
+   var len = node.childNodes.length;
+
+	while (node.hasChildNodes())
+	{
+	  node.removeChild(node.firstChild);
+	}
+}
+
+function onMsgEvent(event) {
+   // Break apart the text string into screen name and message parts.
+   var symbol = event.data.symbol;
+   var price = event.data.price;
+   var pricechange = event.data.change;
+   //alert("symbol: "+symbol+" price: "+price+" change: "+pricechange);
+
+   var pricenode = dojo.byId("price."+symbol);
+   var changenode = dojo.byId("change."+symbol);
+   removeChildrenFromNode(pricenode);
+   removeChildrenFromNode(changenode);
+   var pricelabel = document.createTextNode(price);
+   pricelabel.value = price;
+   var changelabel = document.createTextNode(pricechange);
+   changelabel.value = pricechange;
+   pricenode.appendChild(pricelabel);
+   changenode.appendChild(changelabel);
+
+   var table = dojo.byId("stocktable");
+   var rows = table.getElementsByTagName("tr");
+   for(i = 0; i < rows.length; i++){
+	   if (rows[i].bgColor != "gray") {
+	       rows[i].bgColor = "white";
+	   }
+   }
+   //manipulate rows
+   var rowCurrent = dojo.byId("row."+symbol);
+   if (pricechange<=0) {
+       rowCurrent.bgColor = "red";
+   } else {
+	   rowCurrent.bgColor = "cyan";
+   }
+}
+
+
+</script>
+</head>
+<body bgcolor="#ffffff">
+<h1 align="center">Bayeux Stock Ticker</h1>
+<h2 align="left"> &nbsp;</h2>
+<p>
+<table id="stocktable" cellspacing="0" cellpadding="3" width="100%" align="center" border="0">
+  <tr id="row.HEADER">
+    <td>SYMBOL</td>
+    <td>PRICE</td>
+    <td>LAST CHANGE</td>
+    <td>SUBSCRIBE</td></tr>
+  <tr id="row.SPRG">
+    <td>SPRG</td>
+    <td id="price.SPRG"></td>
+    <td id="change.SPRG"></td>
+    <td id="check.SPRG"><input type="checkbox" id="check.SPRG" checked onClick="subscribe(this,'SPRG')"></td>
+  </tr>
+  <tr id="row.GOOG">
+    <td>GOOG</td>
+    <td id="price.GOOG"></td>
+    <td id="change.GOOG"></td>
+    <td id="check.GOOG"><input type="checkbox" id="check.GOOG" checked  onClick="subscribe(this,'GOOG')"></td>
+  </tr>
+  <tr id="row.YHOO">
+    <td>YHOO</td>
+    <td id="price.YHOO"></td>
+    <td id="change.YHOO"></td>
+    <td id="check.YHOO"><input type="checkbox" id="check.GOOG" checked  onClick="subscribe(this,'YHOO')"></td>
+  </tr>
+</table>
+</p>
+</body>
+</html>
\ No newline at end of file
diff --git a/res/cobertura/logback.xml b/modules/bayeux/webapps/cometd/index.html
similarity index 64%
rename from res/cobertura/logback.xml
rename to modules/bayeux/webapps/cometd/index.html
index 76f3186..f7f722e 100644
--- a/res/cobertura/logback.xml
+++ b/modules/bayeux/webapps/cometd/index.html
@@ -1,4 +1,3 @@
-<?xml version="1.0"?>
 <!--
   Licensed to the Apache Software Foundation (ASF) under one or more
   contributor license agreements.  See the NOTICE file distributed with
@@ -15,17 +14,9 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<configuration>
+<h1>Cometd demo</h1>
 
-  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
-    <!-- encoders are assigned the type
-         ch.qos.logback.classic.encoder.PatternLayoutEncoder by default -->
-    <encoder>
-      <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
-    </encoder>
-  </appender>
-
-  <root level="INFO">
-    <appender-ref ref="STDOUT" />
-  </root>
-</configuration>
\ No newline at end of file
+<p>
+Try the <a href="examples/simplechat/cometdchat.htm">Simple Chat Demo</a>.</br>
+Try the <a href="examples/simplechat/ticker.html">Stock Ticker Demo</a>.</br>
+</p>
diff --git a/modules/jdbc-pool/build.properties.default b/modules/jdbc-pool/build.properties.default
index 7d76426..f15ecac 100644
--- a/modules/jdbc-pool/build.properties.default
+++ b/modules/jdbc-pool/build.properties.default
@@ -35,8 +35,8 @@
 # contexts by the various build scripts.
 base.path=${basedir}/includes
 
-compile.source=1.8
-compile.target=1.8
+compile.source=1.7
+compile.target=1.7
 compile.debug=true
 
 # Do not pass -deprecation (-Xlint:deprecation) flag to javac
diff --git a/modules/jdbc-pool/pom.xml b/modules/jdbc-pool/pom.xml
index 62a752b..51bfc2c 100644
--- a/modules/jdbc-pool/pom.xml
+++ b/modules/jdbc-pool/pom.xml
@@ -90,8 +90,8 @@
         <groupId>org.apache.maven.plugins</groupId>
         <artifactId>maven-compiler-plugin</artifactId>
         <configuration>
-          <source>1.8</source>
-          <target>1.8</target>
+          <source>1.7</source>
+          <target>1.7</target>
           <optimize>true</optimize>
           <debug>true</debug>
           <showDeprecation>true</showDeprecation>
diff --git a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestStatementCache.java b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestStatementCache.java
index 3a8778a..b6bb4d3 100644
--- a/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestStatementCache.java
+++ b/modules/jdbc-pool/src/test/java/org/apache/tomcat/jdbc/test/TestStatementCache.java
@@ -166,6 +166,7 @@
         Connection con1 = datasource.getConnection();
         Connection con2 = datasource.getConnection();
         for (int i=0; i<120; i++) {
+            @SuppressWarnings("resource") // Connections are closed below
             Connection con = (i%2==0)?con1:con2;
             PreparedStatement ps = con.prepareStatement("select "+i);
             ps.close();
diff --git a/modules/tomcat-lite/.classpath b/modules/tomcat-lite/.classpath
new file mode 100644
index 0000000..bfb9921
--- /dev/null
+++ b/modules/tomcat-lite/.classpath
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry excluding="org/apache/tomcat/lite/servlet/ServletApi30.java" kind="src" path="java"/>
+	<classpathentry excluding="org/apache/coyote/servlet/" kind="src" path="test"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
+	<classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/3"/>
+	<classpathentry kind="lib" path="target/lib/annotations-api.jar"/>
+	<classpathentry kind="lib" path="target/lib/ant-launcher.jar"/>
+	<classpathentry kind="lib" path="target/lib/ant.jar"/>
+	<classpathentry kind="lib" path="target/lib/asm-tree.jar"/>
+	<classpathentry kind="lib" path="target/lib/asm.jar"/>
+	<classpathentry kind="lib" path="target/lib/catalina.jar"/>
+	<classpathentry kind="lib" path="target/lib/commons-codec.jar"/>
+	<classpathentry kind="lib" path="target/lib/coyote.jar"/>
+	<classpathentry kind="lib" path="target/lib/el-api.jar"/>
+	<classpathentry kind="lib" path="target/lib/jasper-el.jar"/>
+	<classpathentry kind="lib" path="target/lib/jasper-jdt.jar"/>
+	<classpathentry kind="lib" path="target/lib/jasper.jar"/>
+	<classpathentry kind="lib" path="target/lib/jsp-api.jar"/>
+	<classpathentry kind="lib" path="target/lib/juli.jar"/>
+	<classpathentry kind="lib" path="target/lib/junit.jar"/>
+	<classpathentry kind="lib" path="target/lib/jzlib.jar"/>
+	<classpathentry kind="lib" path="target/lib/servlet-api.jar"/>
+	<classpathentry kind="output" path="output/classes"/>
+</classpath>
diff --git a/modules/tomcat-lite/.project b/modules/tomcat-lite/.project
new file mode 100644
index 0000000..b4c826d
--- /dev/null
+++ b/modules/tomcat-lite/.project
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>tomcat-lite</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/modules/tomcat-lite/build.xml b/modules/tomcat-lite/build.xml
new file mode 100644
index 0000000..d2c79dd
--- /dev/null
+++ b/modules/tomcat-lite/build.xml
@@ -0,0 +1,319 @@
+<?xml version="1.0"?>
+<project name="Tomcat Lite" default="tomcat-lite.jar"
+         xmlns:ivy="antlib:org.apache.ivy.ant"
+         basedir=".">
+
+    <property file="${user.home}/build.properties" />
+    <property file="${basedir}/build.properties" />
+    <property file="${basedir}/build.properties.default" />
+
+    <property name="tomcat.base" value="${basedir}/../.." />
+    <property name="tomcat.src" value="${tomcat.base}/java" />
+    <property name="tomcat.build" value="${tomcat.base}/output/build" />
+
+    <property name="tomcat.lite.src" value="${basedir}/" />
+    <property name="classes" value="${basedir}/target/tomcat-lite/classes" />
+    <property name="test-classes" value="${basedir}/target/tomcat-lite/test-classes" />
+    <property name="http.test-classes" value="${basedir}/target/tomcat-lite/http-test-classes" />
+    <property name="jar.dir" value="${basedir}/target/tomcat-lite/" />
+    <property name="MAIN" value="org.apache.tomcat.integration.simple.Main"/>
+    <property name="compile.source" value="1.6"/>
+    <property name="compile.debug" value="true"/>
+
+    <!-- All Ivy downloads -->
+    <path id='lite-classpath'>
+        <fileset dir='target/lib' includes="*.jar" />
+    </path>
+
+    <path id='head-classpath'>
+        <pathelement location="${tomcat.build}/../classes" />
+        <pathelement location="target/lib/asm.jar" />
+        <pathelement location="target/lib/asm-tree.jar" />
+        <pathelement location="target/lib/junit.jar"/>
+        <pathelement location="target/lib/commons-codec.jar"/>
+    </path>
+
+    <target name="http" depends="http.compile,http.test,http.pack"/>
+
+    <target name="http.compile"
+           description="Compile only the HTTP connector classes, no servlets">
+        <mkdir dir="${classes}"/>
+        <javac destdir="${classes}"
+               debug="${compile.debug}"
+               deprecation="${compile.deprecation}"
+               source="${compile.source}"
+               encoding="ISO-8859-1">
+            <classpath refid="lite-classpath" />
+            <src path="${tomcat.lite.src}/java" />
+            <classpath refid="head-classpath" />
+            <exclude name="org/apache/tomcat/servlets/**"/>
+            <exclude name="org/apache/tomcat/lite/servlet/**"/>
+        </javac>
+        <copy todir="${classes}">
+            <fileset dir="${tomcat.lite.src}/java"
+                includes="**/*.properties **/*.xml" />
+        </copy>
+    </target>
+
+    <target name="http.test.compile"
+           description="Test only the HTTP connector classes, no servlets">
+        <mkdir dir="${http.test-classes}"/>
+        <javac destdir="${http.test-classes}"
+                debug="${compile.debug}"
+                deprecation="${compile.deprecation}"
+                source="${compile.source}"
+                encoding="ISO-8859-1"
+         >
+             <classpath refid="lite-classpath" />
+             <classpath refid="head-classpath" />
+             <classpath path="${classes}" />
+             <src path="${tomcat.lite.src}/test" />
+            <exclude name="org/apache/tomcat/lite/servlet/**"/>
+            <exclude name="org/apache/coyote/lite/**"/>
+            <exclude name="org/apache/tomcat/test/watchdog/**"/>
+         </javac>
+        <copy todir="${http.test-classes}">
+            <fileset dir="${tomcat.lite.src}/test"
+                includes="**/*.properties **/*.xml  **/*.bin **/*.keystore **/*.cert **/*.der" />
+        </copy>
+   </target>
+
+      <target name="http.test" depends="http.test.compile" >
+          <!-- also: perTest(default) -->
+          <junit printsummary="on" fork="once"
+                    timeout="600000" maxmemory="1G" outputtoformatters="no"
+                    >
+              <classpath refid="lite-classpath" />
+              <classpath refid="head-classpath" />
+              <classpath path="${http.test-classes}" />
+              <classpath path="${classes}" />
+
+            <formatter type="brief" usefile="false" />
+
+            <batchtest>
+              <fileset dir="test" >
+                 <!-- Include all by default -->
+                  <include name="**/*Test.java" />
+                  <include name="**/*Tests.java" />
+                 <!-- Exclude TestAll ortherwise there will be duplicated -->
+                 <exclude name="**/TestAll.java" />
+                 <!-- Exclude helper classes -->
+                 <exclude name="**/Tester*.java" />
+
+                     <exclude name="org/apache/tomcat/lite/servlet/**"/>
+                     <exclude name="org/apache/coyote/lite/**"/>
+                     <exclude name="org/apache/tomcat/test/watchdog/**"/>
+              </fileset>
+             </batchtest>
+              </junit>
+    </target>
+
+    <target name="runtest" depends="http.test.compile">
+        <junit printsummary="withOutAndErr" fork="no" dir="${tomcat.base}">
+            <sysproperty key="tests" value="${tests}"/>
+            <classpath refid="lite-classpath" />
+            <classpath refid="head-classpath" />
+            <classpath path="${http.test-classes}" />
+            <classpath path="${classes}" />
+
+            <formatter type="plain" usefile="false" />
+
+          <batchtest>
+            <fileset dir="test" >
+                <include name="**/${test}.java" />
+            </fileset>
+           </batchtest>
+            </junit>
+
+     </target>
+
+    <target name="http.pack"
+           description="Pack the HTTP client and connector">
+    </target>
+
+    <target name="compile"
+           description="Build all classes against tomcat head.">
+        <mkdir dir="${classes}"/>
+        <javac destdir="${classes}"
+               debug="${compile.debug}"
+               deprecation="${compile.deprecation}"
+               source="${compile.source}"
+               encoding="ISO-8859-1">
+            <classpath refid="lite-classpath" />
+            <src path="${tomcat.lite.src}/java" />
+            <classpath refid="head-classpath" />
+            <exclude name="**/ServletApi25.java"/>
+        </javac>
+        <copy todir="${classes}">
+            <fileset dir="${tomcat.lite.src}/java"
+                includes="**/*.properties **/*.xml" />
+        </copy>
+    </target>
+
+    <target name="compile25"
+        description="Build against 2.5 servlet API, similar with the maven">
+        <mkdir dir="${classes}" />
+        <javac destdir="${classes}"
+               debug="${compile.debug}"
+               deprecation="${compile.deprecation}"
+               source="${compile.source}"
+               encoding="ISO-8859-1">
+            <classpath refid="lite-classpath" />
+            <src path="${tomcat.lite.src}/java" />
+            <exclude name="**/ServletApi30.java"/>
+            <exclude name="org/apache/tomcat/coyote/servlet/*.java"/>
+        </javac>
+        <copy todir="${classes}">
+            <fileset dir="${tomcat.lite.src}/java"
+                includes="**/*.properties **/*.xml" />
+        </copy>
+    </target>
+
+    <target name="test" depends="test30,test25"/>
+
+    <target name="test30" depends="compile">
+        <mkdir dir="${test-classes}"/>
+        <javac destdir="${test-classes}"
+               debug="${compile.debug}"
+               deprecation="${compile.deprecation}"
+               source="${compile.source}"
+               encoding="ISO-8859-1"
+        >
+            <classpath refid="lite-classpath" />
+            <classpath refid="head-classpath" />
+            <classpath path="${classes}" />
+            <src path="${tomcat.lite.src}/test" />
+        </javac>
+        <copy todir="${test-classes}">
+            <fileset dir="${tomcat.lite.src}/test"
+                includes="**/*.properties **/*.xml  **/*.keystore" />
+        </copy>
+
+        <!-- Need to run it in tomcat to find output/build/webapps -->
+        <junit printsummary="yes" fork="yes" dir="${tomcat.base}"
+            >
+            <classpath refid="lite-classpath" />
+            <classpath refid="head-classpath" />
+            <classpath path="${test-classes}" />
+            <classpath path="${classes}" />
+
+          <formatter type="plain" usefile="false" />
+
+          <batchtest>
+            <fileset dir="test" >
+              <!-- Include all by default -->
+                <include name="**/*Test.java" />
+                <include name="**/*Tests.java" />
+              <!-- Exclude TestAll ortherwise there will be duplicated -->
+              <exclude name="**/TestAll.java" />
+              <!-- Exclude helper classes -->
+              <exclude name="**/Tester*.java" />
+            </fileset>
+           </batchtest>
+            </junit>
+    </target>
+
+    <target name="test25" depends="compile25">
+        <mkdir dir="${test-classes}"/>
+        <javac destdir="${test-classes}"
+               debug="${compile.debug}"
+               deprecation="${compile.deprecation}"
+               source="${compile.source}"
+               encoding="ISO-8859-1"
+        >
+            <classpath refid="lite-classpath" />
+            <classpath path="${classes}" />
+            <classpath path="target/lib/junit.jar"/>
+            <classpath path="target/lib/commons-codec.jar"/>
+            <src path="${tomcat.lite.src}/test" />
+            <exclude name="org/apache/coyote/**"/>
+        </javac>
+        <copy todir="${test-classes}">
+            <fileset dir="${tomcat.lite.src}/test"
+                includes="**/*.properties **/*.xml **/*.keystore" />
+        </copy>
+
+        <junit printsummary="yes" fork="yes">
+            <classpath refid="lite-classpath" />
+            <classpath path="${test-classes}" />
+            <classpath path="${classes}" />
+
+          <formatter type="plain" usefile="false" />
+
+          <batchtest>
+            <fileset dir="test" >
+                <exclude name="org/apache/coyote/**" />
+              <!-- Include all by default -->
+              <include name="**/*Test.java" />
+              <include name="**/*Tests.java" />
+              <!-- Exclude TestAll ortherwise there will be duplicated -->
+                <exclude name="**/TestAll.java" />
+              <!-- Exclude helper classes -->
+              <exclude name="**/Tester*.java" />
+            </fileset>
+           </batchtest>
+            </junit>
+    </target>
+
+    <target name="clean">
+        <delete dir="${classes}" includes="**" />
+        <delete dir="${jar.dir}" includes="**" />
+        <delete dir="${jar.dir}/jar" includes="**" />
+    </target>
+
+    <target name="tomcat-lite.jar" depends="compile,pack_tomcat-lite.jar" />
+
+    <target name="pack_tomcat-lite.jar">
+        <mkdir dir="${jar.dir}/jar" />
+        <jar destfile="${jar.dir}/jar/tomcat-lite.jar">
+            <manifest>
+                <attribute name="Main-Class" value="${MAIN}"/>
+            </manifest>
+            <fileset dir="${classes}">
+                <include name ="org/apache/tomcat/lite/**" />
+                <include name ="org/apache/tomcat/servlets/**" />
+            </fileset>
+        </jar>
+    </target>
+
+    <target name="run">
+        <java jar="${jar.dir}/tomcat-lite.jar"/>
+    </target>
+
+    <!-- Boilreplate for dependencies -->
+
+    <property name="ivy.install.version" value="2.1.0" />
+    <condition property="ivy.home" value="${env.IVY_HOME}">
+        <isset property="env.IVY_HOME" />
+    </condition>
+    <property name="ivy.home" value="${basedir}/target/ivy" />
+    <property name="ivy.jar.dir" value="${ivy.home}/lib" />
+    <property name="ivy.jar.file" value="${ivy.jar.dir}/ivy.jar" />
+    <available file="${ivy.jar.file}" property="ivy.exist"/>
+
+    <target name="download-ivy" unless="ivy.exist">
+        <mkdir dir="${ivy.jar.dir}"/>
+        <get src="http://repo2.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
+                 dest="${ivy.jar.file}" usetimestamp="true"/>
+    </target>
+
+    <target name="init-ivy" depends="download-ivy">
+        <!-- try to load ivy here from ivy home, in case the user has not already dropped
+                  it into ant's lib dir (note that the latter copy will always take precedence).
+                  We will not fail as long as local lib dir exists (it may be empty) and
+                  ivy is in at least one of ant's lib dir or the local lib dir. -->
+        <path id="ivy.lib.path">
+            <fileset dir="${ivy.jar.dir}" includes="*.jar"/>
+
+        </path>
+        <taskdef resource="org/apache/ivy/ant/antlib.xml"
+                     uri="antlib:org.apache.ivy.ant" classpathref="ivy.lib.path"/>
+    </target>
+
+    <target name="download" depends="init-ivy">
+        <mkdir dir="target/lib"/>
+        <ivy:resolve file="pom.xml" conf="compile" />
+        <ivy:retrieve pattern="target/lib/[artifact].[ext]"/>
+    </target>
+
+</project>
diff --git a/modules/tomcat-lite/ivy.xml b/modules/tomcat-lite/ivy.xml
new file mode 100644
index 0000000..2034732
--- /dev/null
+++ b/modules/tomcat-lite/ivy.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<!--
+   Licensed to the Apache Software Foundation (ASF) under one
+   or more contributor license agreements.  See the NOTICE file
+   distributed with this work for additional information
+   regarding copyright ownership.  The ASF licenses this file
+   to you 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.
+-->
+<ivy-module version="2.0"
+       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xsi:noNamespaceSchemaLocation="http://ant.apache.org/ivy/schemas/ivy.xsd">
+    <info
+        organisation="org.apache.tomcat.lite"
+        module="tomcat-lite"
+        status="integration">
+	</info>
+	
+	<dependencies>
+	
+	</dependencies>
+</ivy-module>
diff --git a/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java b/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java
new file mode 100644
index 0000000..c7817d4
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/coyote/lite/LiteProtocolHandler.java
@@ -0,0 +1,426 @@
+package org.apache.coyote.lite;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.apache.coyote.ActionCode;
+import org.apache.coyote.ActionHook;
+import org.apache.coyote.Adapter;
+import org.apache.coyote.InputBuffer;
+import org.apache.coyote.OutputBuffer;
+import org.apache.coyote.ProtocolHandler;
+import org.apache.coyote.Request;
+import org.apache.coyote.Response;
+import org.apache.tomcat.lite.http.HttpClient;
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpConnectionPool;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpServer;
+import org.apache.tomcat.lite.http.MultiMap;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer;
+import org.apache.tomcat.lite.http.HttpConnector.HttpChannelEvents;
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.http.MultiMap.Entry;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.SocketConnector;
+import org.apache.tomcat.lite.io.SslProvider;
+import org.apache.tomcat.util.buf.ByteChunk;
+import org.apache.tomcat.util.buf.MessageBytes;
+import org.apache.tomcat.util.http.MimeHeaders;
+import org.apache.tomcat.util.modeler.Registry;
+
+/**
+ * Work in progress - use the refactored http as a coyote connector.
+ * Just basic requests work right now - need to implement all the
+ * methods of coyote.
+ *
+ *
+ * @author Costin Manolache
+ */
+public class LiteProtocolHandler implements ProtocolHandler {
+
+    Adapter adapter;
+    Map<String, Object> attributes = new HashMap<String, Object>();
+
+
+    HttpConnector httpConnServer;
+    int port = 8999;
+
+    // Tomcat JMX integration
+    Registry registry;
+
+    public LiteProtocolHandler() {
+    }
+
+    @Override
+    public void destroy() throws Exception {
+    }
+
+    @Override
+    public Adapter getAdapter() {
+        return adapter;
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        // TODO: dynamic
+        return attributes.get(name);
+    }
+
+    @Override
+    public Iterator<String> getAttributeNames() {
+        return attributes.keySet().iterator();
+    }
+
+    @Override
+    public void init() throws Exception {
+        registry = Registry.getRegistry(null, null);
+        httpConnServer = HttpServer.newServer(port);
+
+        httpConnServer.getDispatcher().setDefaultService(new HttpService() {
+            @Override
+            public void service(HttpRequest httpReq, HttpResponse httpRes)
+                    throws IOException {
+                coyoteService(httpReq, httpRes);
+            }
+
+        });
+        final String base = "" + port;
+        bind("Httpconnector-" + port, httpConnServer);
+        bind("HttpconnectorPool-" + port, httpConnServer.cpool);
+        IOConnector io = httpConnServer.getIOConnector();
+        int ioLevel = 0;
+        while (io != null) {
+            bind("IOConnector-" + (ioLevel++) + "-" + base, io);
+            if (io instanceof SocketConnector) {
+                bind("NioThread-" + base,
+                        ((SocketConnector) io).getSelector());
+
+            }
+            io = io.getNet();
+        }
+        httpConnServer.cpool.setEvents(new HttpConnectionPool.HttpConnectionPoolEvents() {
+
+            @Override
+            public void closedConnection(RemoteServer host, HttpConnection con) {
+                unbind("HttpConnection-" + base + "-" + con.getId());
+            }
+
+            @Override
+            public void newConnection(RemoteServer host, HttpConnection con) {
+                bind("HttpConnection-" + base + "-" + con.getId(), con);
+            }
+
+            @Override
+            public void newTarget(RemoteServer host) {
+                bind("AsyncHttp-" + base + "-" + host.target, host);
+            }
+
+            @Override
+            public void targetRemoved(RemoteServer host) {
+                unbind("AsyncHttp-" + base + "-" + host.target);
+            }
+
+        });
+
+        httpConnServer.setOnCreate(new HttpChannelEvents() {
+            @Override
+            public void onCreate(HttpChannel data, HttpConnector extraData)
+                    throws IOException {
+                bind("AsyncHttp-" + base + "-" + data.getId(), data);
+            }
+            @Override
+            public void onDestroy(HttpChannel data, HttpConnector extraData)
+                    throws IOException {
+                unbind("AsyncHttp-" + base + "-" + data.getId());
+            }
+        });
+
+        // TODO: process attributes via registry !!
+
+    }
+
+    private void bind(String name, Object o) {
+        try {
+            registry.registerComponent(o, "TomcatLite:name=" + name, null);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    private void unbind(String name) {
+        registry.unregisterComponent("name=" + name);
+    }
+
+    @Override
+    public void pause() throws Exception {
+    }
+
+    @Override
+    public void resume() throws Exception {
+    }
+
+    @Override
+    public void setAdapter(Adapter adapter) {
+        this.adapter = adapter;
+
+    }
+
+    @Override
+    public void setAttribute(String name, Object value) {
+        attributes.put(name, value);
+    }
+
+    @Override
+    public void start() throws Exception {
+        httpConnServer.start();
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    /**
+     * Wrap old tomcat buffer to lite buffer.
+     */
+    private void wrap(MessageBytes dest, CBuffer buffer) {
+        dest.setChars(buffer.array(), buffer.position(),
+                buffer.length());
+    }
+
+    /**
+     * Main lite service method, will wrap to coyote request
+     */
+    private void coyoteService(final HttpRequest httpReq, final HttpResponse httpRes) {
+        // TODO: reuse, per req
+        RequestData rc = new RequestData();
+        rc.init(httpReq, httpRes);
+
+        try {
+            adapter.service(rc.req, rc.res);
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    /**
+     * ActionHook implementation, include coyote request/response objects.
+     */
+    public class RequestData implements ActionHook {
+        private final class LiteOutputBuffer implements OutputBuffer {
+            @Override
+            public int doWrite(org.apache.tomcat.util.buf.ByteChunk chunk,
+                    Response response) throws IOException {
+                httpRes.getBody().append(chunk.getBuffer(), chunk.getStart(),
+                        chunk.getLength());
+                return chunk.getLength();
+            }
+        }
+
+        OutputBuffer outputBuffer = new LiteOutputBuffer();
+        // TODO: recycle, etc.
+        Request req = new Request();
+
+        Response res = new Response();
+        HttpResponse httpRes;
+        HttpRequest httpReq;
+
+        InputBuffer inputBuffer = new InputBuffer() {
+            @Override
+            public int doRead(ByteChunk bchunk, Request request)
+                    throws IOException {
+                httpReq.getBody().waitData(httpReq.getHttpChannel().getIOTimeout());
+                int rd =
+                    httpReq.getBody().read(bchunk.getBytes(),
+                        bchunk.getStart(), bchunk.getBytes().length);
+                if (rd > 0) {
+                    bchunk.setEnd(bchunk.getEnd() + rd);
+                }
+                return rd;
+            }
+        };
+
+        public RequestData() {
+            req.setInputBuffer(inputBuffer);
+            res.setOutputBuffer(outputBuffer);
+            req.setResponse(res);
+            res.setRequest(req);
+            res.setHook(this);
+        }
+
+        public void init(HttpRequest httpReq, HttpResponse httpRes) {
+            this.httpRes = httpRes;
+            this.httpReq = httpReq;
+            // TODO: turn http request into a coyote request - copy all fields,
+            // add hooks where needed.
+
+            wrap(req.decodedURI(), httpReq.decodedURI());
+            wrap(req.method(), httpReq.method());
+            wrap(req.protocol(), httpReq.protocol());
+            wrap(req.requestURI(), httpReq.requestURI());
+            wrap(req.queryString(), httpReq.queryString());
+
+            req.setServerPort(httpReq.getServerPort());
+            req.serverName().setString(req.localName().toString());
+
+            MultiMap mimeHeaders = httpReq.getMimeHeaders();
+            MimeHeaders coyoteHeaders = req.getMimeHeaders();
+            for (int i = 0; i < mimeHeaders.size(); i++ ) {
+                Entry entry = mimeHeaders.getEntry(i);
+                MessageBytes val =
+                    coyoteHeaders.addValue(entry.getName().toString());
+                val.setString(entry.getValue().toString());
+            }
+        }
+
+        /**
+         * Send an action to the connector.
+         *
+         * @param actionCode Type of the action
+         * @param param Action parameter
+         */
+        public void action(ActionCode actionCode, Object param) {
+
+            if (actionCode == ActionCode.ACTION_POST_REQUEST) {
+                commit(); // make sure it's sent - on errors
+            } else if (actionCode == ActionCode.ACTION_COMMIT) {
+                commit();
+            } else if (actionCode == ActionCode.ACTION_ACK) {
+                // Done automatically by http connector
+            } else if (actionCode == ActionCode.ACTION_CLIENT_FLUSH) {
+                try {
+                    httpReq.send();
+                } catch (IOException e) {
+                    httpReq.getHttpChannel().abort(e);
+                    res.setErrorException(e);
+                }
+
+            } else if (actionCode == ActionCode.ACTION_CLOSE) {
+                // Close
+
+                // End the processing of the current request, and stop any further
+                // transactions with the client
+
+//                comet = false;
+//                try {
+//                    outputBuffer.endRequest();
+//                } catch (IOException e) {
+//                    // Set error flag
+//                    error = true;
+//                }
+
+            } else if (actionCode == ActionCode.ACTION_RESET) {
+                // Reset response
+                // Note: This must be called before the response is committed
+                httpRes.getBody().clear();
+
+            } else if (actionCode == ActionCode.ACTION_CUSTOM) {
+
+                // Do nothing
+
+            } else if (actionCode == ActionCode.ACTION_REQ_HOST_ADDR_ATTRIBUTE) {
+                req.remoteAddr().setString(httpReq.remoteAddr().toString());
+            } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_NAME_ATTRIBUTE) {
+                req.localName().setString(httpReq.localName().toString());
+            } else if (actionCode == ActionCode.ACTION_REQ_HOST_ATTRIBUTE) {
+                req.remoteHost().setString(httpReq.remoteHost().toString());
+            } else if (actionCode == ActionCode.ACTION_REQ_LOCAL_ADDR_ATTRIBUTE) {
+                req.localAddr().setString(httpReq.localAddr().toString());
+            } else if (actionCode == ActionCode.ACTION_REQ_REMOTEPORT_ATTRIBUTE) {
+                req.setRemotePort(httpReq.getRemotePort());
+            } else if (actionCode == ActionCode.ACTION_REQ_LOCALPORT_ATTRIBUTE) {
+                req.setLocalPort(httpReq.getLocalPort());
+            } else if (actionCode == ActionCode.ACTION_REQ_SSL_ATTRIBUTE ) {
+
+                Object sslAtt = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_CIPHER);
+                req.setAttribute("javax.servlet.request.cipher_suite", sslAtt);
+
+                sslAtt = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_KEY_SIZE);
+                req.setAttribute("javax.servlet.request.key_size", sslAtt);
+
+                sslAtt = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_SESSION_ID);
+                req.setAttribute("javax.servlet.request.ssl_session", sslAtt);
+
+            } else if (actionCode == ActionCode.ACTION_REQ_SSL_CERTIFICATE) {
+
+                Object cert = httpReq.getHttpChannel().getNet().getAttribute(SslProvider.ATT_SSL_CERT);
+                req.setAttribute("javax.servlet.request.X509Certificate", cert);
+
+            } else if (actionCode == ActionCode.ACTION_REQ_SET_BODY_REPLAY) {
+                ByteChunk body = (ByteChunk) param;
+                httpReq.getBody().clear();
+                try {
+                    httpReq.getBody().append(body.getBuffer(), body.getStart(), body.getLength());
+                } catch (IOException e) {
+                    e.printStackTrace();
+                }
+
+            } else if (actionCode == ActionCode.ACTION_AVAILABLE) {
+                req.setAvailable(httpReq.getBody().available());
+            } else if (actionCode == ActionCode.ACTION_COMET_BEGIN) {
+//                comet = true;
+            } else if (actionCode == ActionCode.ACTION_COMET_END) {
+//                comet = false;
+            } else if (actionCode == ActionCode.ACTION_COMET_CLOSE) {
+                //no op
+            } else if (actionCode == ActionCode.ACTION_COMET_SETTIMEOUT) {
+                //no op
+//            } else if (actionCode == ActionCode.ACTION_ASYNC_START) {
+//                //TODO SERVLET3 - async
+//            } else if (actionCode == ActionCode.ACTION_ASYNC_COMPLETE) {
+//                //TODO SERVLET3 - async
+//            } else if (actionCode == ActionCode.ACTION_ASYNC_SETTIMEOUT) {
+//                //TODO SERVLET3 - async
+            }
+
+
+        }
+
+        private void commit() {
+            if (res.isCommitted())
+                return;
+
+            // TODO: copy headers, fields
+            httpRes.setStatus(res.getStatus());
+            httpRes.setMessage(res.getMessage());
+            MultiMap mimeHeaders = httpRes.getMimeHeaders();
+            MimeHeaders coyoteHeaders = res.getMimeHeaders();
+            for (int i = 0; i < coyoteHeaders.size(); i++ ) {
+                MessageBytes name = coyoteHeaders.getName(i);
+                MessageBytes val = coyoteHeaders.getValue(i);
+                Entry entry = mimeHeaders.addEntry(name.toString());
+                entry.getValue().set(val.toString());
+            }
+            String contentType = res.getContentType();
+            if (contentType != null) {
+                mimeHeaders.addEntry("Content-Type").getValue().set(contentType);
+            }
+            String contentLang = res.getContentType();
+            if (contentLang != null) {
+                mimeHeaders.addEntry("Content-Language").getValue().set(contentLang);
+            }
+            long contentLength = res.getContentLengthLong();
+            if (contentLength != -1) {
+                httpRes.setContentLength(contentLength);
+            }
+            String lang = res.getContentLanguage();
+            if (lang != null) {
+                httpRes.setHeader("Content-Language", lang);
+            }
+
+            try {
+                httpReq.send();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/BaseMapper.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/BaseMapper.java
new file mode 100644
index 0000000..6b2075b
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/BaseMapper.java
@@ -0,0 +1,1112 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.FileConnector;
+import org.apache.tomcat.lite.io.BBucket;
+
+/**
+ * Mapper, which implements the servlet API mapping rules (which are derived
+ * from the HTTP rules).
+ *
+ * This class doesn't use JNDI.
+ */
+public class BaseMapper {
+
+    private static Logger logger =
+        Logger.getLogger(BaseMapper.class.getName());
+
+    // TODO:
+    /**
+     * Mapping should be done on bytes - as received from net, before
+     * translation to chars. This would allow setting the default charset
+     * for the context - or even executing the servlet and letting it specify
+     * the charset to use for further decoding.
+     *
+     */
+    public static interface Mapper {
+        public void map(BBucket host, BBucket url, MappingData md);
+    }
+
+
+    /**
+     * Like BaseMapper, for a Context.
+     */
+    public static class ServiceMapper extends BaseMapper {
+        /**
+         * Context associated with this wrapper, used for wrapper mapping.
+         */
+        public BaseMapper.Context contextMapElement = new BaseMapper.Context(this);
+
+        /**
+         * Set context, used for wrapper mapping (request dispatcher).
+         *
+         * @param welcomeResources Welcome files defined for this context
+         */
+        public void setContext(String path, String[] welcomeResources) {
+            contextMapElement.name = path;
+            contextMapElement.welcomeResources = welcomeResources;
+        }
+
+
+        /**
+         * Add a wrapper to the context associated with this wrapper.
+         *
+         * @param path Wrapper mapping
+         * @param wrapper The Wrapper object
+         */
+        public void addWrapper(String path, Object wrapper) {
+            addWrapper(contextMapElement, path, wrapper);
+        }
+
+
+        public void addWrapper(String path, Object wrapper, boolean jspWildCard) {
+            addWrapper(contextMapElement, path, wrapper, jspWildCard);
+        }
+
+
+        /**
+         * Remove a wrapper from the context associated with this wrapper.
+         *
+         * @param path Wrapper mapping
+         */
+        public void removeWrapper(String path) {
+            removeWrapper(contextMapElement, path);
+        }
+
+
+//        /**
+//         * Map the specified URI relative to the context,
+//         * mutating the given mapping data.
+//         *
+//         * @param uri URI
+//         * @param mappingData This structure will contain the result of the mapping
+//         *                    operation
+//         */
+//        public void map(CBuffer uri, MappingData mappingData)
+//            throws Exception {
+//
+//           CBuffer uricc = uri.getCharBuffer();
+//           internalMapWrapper(contextMapElement, uricc, mappingData);
+//
+//        }
+    }
+
+    /**
+     * Array containing the virtual hosts definitions.
+     */
+    Host[] hosts = new Host[0];
+
+    /**
+     * If no other host is found.
+     * For single-host servers ( most common ) this is the only one
+     * used.
+     */
+    Host defaultHost = new Host();
+
+    public BaseMapper() {
+        defaultHost.contextList = new ContextList();
+    }
+
+    // --------------------------------------------------------- Public Methods
+
+    public synchronized Host addHost(String name) {
+        if (name == null) {
+            name = "localhost";
+        }
+        Host[] newHosts = new Host[hosts.length + 1];
+        Host newHost = new Host();
+        newHost.name = name;
+        newHost.contextList = new ContextList();
+
+        if (insertMap(hosts, newHosts, newHost)) {
+            hosts = newHosts;
+        }
+        return newHost;
+    }
+
+
+    /**
+     * Remove a host from the mapper.
+     *
+     * @param name Virtual host name
+     */
+    public synchronized void removeHost(String name) {
+        // Find and remove the old host
+        int pos = find(hosts, name);
+        if (pos < 0) {
+            return;
+        }
+        Object host = hosts[pos].object;
+        Host[] newHosts = new Host[hosts.length - 1];
+        if (removeMap(hosts, newHosts, name)) {
+            hosts = newHosts;
+        }
+        // Remove all aliases (they will map to the same host object)
+        for (int i = 0; i < newHosts.length; i++) {
+            if (newHosts[i].object == host) {
+                Host[] newHosts2 = new Host[hosts.length - 1];
+                if (removeMap(hosts, newHosts2, newHosts[i].name)) {
+                    hosts = newHosts2;
+                }
+            }
+        }
+    }
+
+    /**
+     * Add an alias to an existing host.
+     * @param name  The name of the host
+     * @param alias The alias to add
+     */
+    public synchronized void addHostAlias(String name, String alias) {
+        int pos = find(hosts, name);
+        if (pos < 0) {
+            // Should not be adding an alias for a host that doesn't exist but
+            // just in case...
+            return;
+        }
+        Host realHost = hosts[pos];
+
+        Host[] newHosts = new Host[hosts.length + 1];
+        Host newHost = new Host();
+        newHost.name = alias;
+        newHost.contextList = realHost.contextList;
+        newHost.object = realHost;
+        if (insertMap(hosts, newHosts, newHost)) {
+            hosts = newHosts;
+        }
+    }
+
+    private Host getHost(String host) {
+        return getHost(CBuffer.newInstance().append(host));
+    }
+
+    private Host getHost(CBuffer host) {
+        if (hosts == null || hosts.length <= 1 || host == null
+                || host.length() == 0 || host.equals("")) {
+            return defaultHost;
+        } else {
+            Host[] hosts = this.hosts;
+            // TODO: if hosts.length == 1 or defaultHost ?
+            int pos = findIgnoreCase(hosts, host);
+            if ((pos != -1) && (host.equalsIgnoreCase(hosts[pos].name))) {
+                return hosts[pos];
+            } else {
+                return defaultHost;
+            }
+        }
+    }
+
+    private Host getOrCreateHost(String hostName) {
+        Host host = getHost(CBuffer.newInstance().append(hostName));
+        if (host == null) {
+            host = addHost(hostName);
+        }
+        return host;
+    }
+
+    // Contexts
+
+    /**
+     * Add a new Context to an existing Host.
+     *
+     * @param hostName Virtual host name this context belongs to
+     * @param path Context path
+     * @param context Context object
+     * @param welcomeResources Welcome files defined for this context
+     * @param resources Static resources of the context
+     * @param ctxService
+     */
+    public BaseMapper.Context addContext(String hostName, String path, Object context,
+            String[] welcomeResources, FileConnector resources,
+            HttpChannel.HttpService ctxService) {
+
+        if (path == null) {
+            path = "/";
+        }
+
+        Host host = getOrCreateHost(hostName);
+
+        int slashCount = slashCount(path);
+        synchronized (host) {
+            BaseMapper.Context[] contexts = host.contextList.contexts;
+            // Update nesting
+            if (slashCount > host.contextList.nesting) {
+                host.contextList.nesting = slashCount;
+            }
+            for (int i = 0; i < contexts.length; i++) {
+                if (path.equals(contexts[i].name)) {
+                    return contexts[i];
+                }
+            }
+            BaseMapper.Context[] newContexts = new BaseMapper.Context[contexts.length + 1];
+            BaseMapper.Context newContext = new BaseMapper.Context(this);
+            newContext.name = path;
+            newContext.object = context;
+            if (welcomeResources != null) {
+                newContext.welcomeResources = welcomeResources;
+            }
+            newContext.resources = resources;
+            if (ctxService != null) {
+                newContext.defaultWrapper = new BaseMapper.ServiceMapping();
+                newContext.defaultWrapper.object = ctxService;
+            }
+
+            if (insertMap(contexts, newContexts, newContext)) {
+                host.contextList.contexts = newContexts;
+            }
+            return newContext;
+        }
+
+    }
+
+
+    /**
+     * Remove a context from an existing host.
+     *
+     * @param hostName Virtual host name this context belongs to
+     * @param path Context path
+     */
+    public void removeContext(String hostName, String path) {
+        Host host = getHost(hostName);
+        synchronized (host) {
+            BaseMapper.Context[] contexts = host.contextList.contexts;
+            if( contexts.length == 0 ){
+                return;
+            }
+            BaseMapper.Context[] newContexts = new BaseMapper.Context[contexts.length - 1];
+            if (removeMap(contexts, newContexts, path)) {
+                host.contextList.contexts = newContexts;
+                // Recalculate nesting
+                host.contextList.nesting = 0;
+                for (int i = 0; i < newContexts.length; i++) {
+                    int slashCount = slashCount(newContexts[i].name);
+                    if (slashCount > host.contextList.nesting) {
+                        host.contextList.nesting = slashCount;
+                    }
+                }
+            }
+        }
+    }
+
+
+    /**
+     * Add a new Wrapper to an existing Context.
+     *
+     * @param hostName Virtual host name this wrapper belongs to
+     * @param contextPath Context path this wrapper belongs to
+     * @param path Wrapper mapping
+     * @param wrapper Wrapper object
+     */
+    public void addWrapper(String hostName, String contextPath, String path,
+                           Object wrapper) {
+        addWrapper(hostName, contextPath, path, wrapper, false);
+    }
+
+
+    public void addWrapper(String hostName, String contextPath, String path,
+                           Object wrapper, boolean jspWildCard) {
+        Host host = getHost(hostName);
+        BaseMapper.Context[] contexts = host.contextList.contexts;
+        int pos2 = find(contexts, contextPath);
+        if( pos2<0 ) {
+            logger.severe("No context found: " + contextPath );
+            return;
+        }
+        BaseMapper.Context context = contexts[pos2];
+        if (context.name.equals(contextPath)) {
+            addWrapper(context, path, wrapper, jspWildCard);
+        }
+    }
+
+
+    public void addWrapper(BaseMapper.Context context, String path, Object wrapper) {
+        addWrapper(context, path, wrapper, false);
+    }
+
+
+    /**
+     * Adds a wrapper to the given context.
+     *
+     * @param context The context to which to add the wrapper
+     * @param path Wrapper mapping
+     * @param wrapper The Wrapper object
+     * @param jspWildCard true if the wrapper corresponds to the JspServlet
+     * and the mapping path contains a wildcard; false otherwise
+     */
+    protected void addWrapper(BaseMapper.Context context, String path, Object wrapper,
+                              boolean jspWildCard) {
+
+        synchronized (context) {
+            BaseMapper.ServiceMapping newWrapper = new BaseMapper.ServiceMapping();
+            newWrapper.object = wrapper;
+            newWrapper.jspWildCard = jspWildCard;
+            if (path.endsWith("/*")) {
+                // Wildcard wrapper
+                newWrapper.name = path.substring(0, path.length() - 2);
+                BaseMapper.ServiceMapping[] oldWrappers = context.wildcardWrappers;
+                BaseMapper.ServiceMapping[] newWrappers =
+                    new BaseMapper.ServiceMapping[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.wildcardWrappers = newWrappers;
+                    int slashCount = slashCount(newWrapper.name);
+                    if (slashCount > context.nesting) {
+                        context.nesting = slashCount;
+                    }
+                }
+            } else if (path.startsWith("*.")) {
+                // Extension wrapper
+                newWrapper.name = path.substring(2);
+                BaseMapper.ServiceMapping[] oldWrappers = context.extensionWrappers;
+                BaseMapper.ServiceMapping[] newWrappers =
+                    new BaseMapper.ServiceMapping[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.extensionWrappers = newWrappers;
+                }
+            } else if (path.equals("/")) {
+                // Default wrapper
+                newWrapper.name = "";
+                context.defaultWrapper = newWrapper;
+            } else {
+                // Exact wrapper
+                newWrapper.name = path;
+                BaseMapper.ServiceMapping[] oldWrappers = context.exactWrappers;
+                BaseMapper.ServiceMapping[] newWrappers =
+                    new BaseMapper.ServiceMapping[oldWrappers.length + 1];
+                if (insertMap(oldWrappers, newWrappers, newWrapper)) {
+                    context.exactWrappers = newWrappers;
+                }
+            }
+        }
+    }
+
+    /**
+     * Remove a wrapper from an existing context.
+     *
+     * @param hostName Virtual host name this wrapper belongs to
+     * @param contextPath Context path this wrapper belongs to
+     * @param path Wrapper mapping
+     */
+    public void removeWrapper(String hostName, String contextPath,
+                              String path) {
+        Host host = getHost(hostName);
+        BaseMapper.Context[] contexts = host.contextList.contexts;
+        int pos2 = find(contexts, contextPath);
+        if (pos2 < 0) {
+            return;
+        }
+        BaseMapper.Context context = contexts[pos2];
+        if (context.name.equals(contextPath)) {
+            removeWrapper(context, path);
+        }
+    }
+
+    protected void removeWrapper(BaseMapper.Context context, String path) {
+        synchronized (context) {
+            if (path.endsWith("/*")) {
+                // Wildcard wrapper
+                String name = path.substring(0, path.length() - 2);
+                BaseMapper.ServiceMapping[] oldWrappers = context.wildcardWrappers;
+                BaseMapper.ServiceMapping[] newWrappers =
+                    new BaseMapper.ServiceMapping[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    // Recalculate nesting
+                    context.nesting = 0;
+                    for (int i = 0; i < newWrappers.length; i++) {
+                        int slashCount = slashCount(newWrappers[i].name);
+                        if (slashCount > context.nesting) {
+                            context.nesting = slashCount;
+                        }
+                    }
+                    context.wildcardWrappers = newWrappers;
+                }
+            } else if (path.startsWith("*.")) {
+                // Extension wrapper
+                String name = path.substring(2);
+                BaseMapper.ServiceMapping[] oldWrappers = context.extensionWrappers;
+                BaseMapper.ServiceMapping[] newWrappers =
+                    new BaseMapper.ServiceMapping[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    context.extensionWrappers = newWrappers;
+                }
+            } else if (path.equals("/")) {
+                // Default wrapper
+                context.defaultWrapper = null;
+            } else {
+                // Exact wrapper
+                String name = path;
+                BaseMapper.ServiceMapping[] oldWrappers = context.exactWrappers;
+                BaseMapper.ServiceMapping[] newWrappers =
+                    new BaseMapper.ServiceMapping[oldWrappers.length - 1];
+                if (removeMap(oldWrappers, newWrappers, name)) {
+                    context.exactWrappers = newWrappers;
+                }
+            }
+        }
+    }
+
+    /**
+     * Map the specified host name and URI, mutating the given mapping data.
+     *
+     * @param host Virtual host name
+     * @param uri URI
+     * @param mappingData This structure will contain the result of the mapping
+     *                    operation
+     */
+    public void map(CBuffer host, CBuffer uri,
+                    MappingData mappingData)
+        throws Exception {
+
+        internalMap(host.length() == 0 ? null :
+            host, uri, mappingData);
+    }
+
+
+    // -------------------------------------------------------- Private Methods
+
+    // public Context mapContext(CBuffer host, CBuffer url);
+
+    /**
+     * Map the specified URI.
+     */
+    private final void internalMap(CBuffer host, CBuffer uri,
+                                   MappingData mappingData)
+        throws Exception {
+        BaseMapper.Context[] contexts = null;
+        BaseMapper.Context context = null;
+        int nesting = 0;
+
+        // Virtual host mapping
+        Host mappedHost = getHost(host);
+        contexts = mappedHost.contextList.contexts;
+        nesting = mappedHost.contextList.nesting;
+
+        // Context mapping
+        if (contexts.length == 0) {
+            return;
+        }
+
+        if (mappingData.context == null) {
+            if (nesting < 1 || contexts.length == 1 && "".equals(contexts[0].name)) {
+                // if 1 context (default) -> fast return
+                context = contexts[0];
+            } else if (nesting == 1) {
+                // if all contexts are 1-component-only
+                int nextSlash = uri.indexOf('/', 1);
+                if (nextSlash == -1) {
+                  nextSlash = uri.length();
+                }
+                mappingData.contextPath.set(uri, 0, nextSlash);
+                int pos = find(contexts, uri);
+                if (pos == -1) {
+                        pos = find(contexts, "/");
+                }
+                if (pos >= 0) {
+                    context = contexts[pos];
+                }
+            } else {
+                int pos = find(contexts, uri);
+                if (pos >= 0) {
+                    int lastSlash = -1;
+                    int length = -1;
+                    boolean found = false;
+                    CBuffer tmp = mappingData.tmpPrefix;
+                    tmp.wrap(uri, 0, uri.length());
+
+                    while (pos >= 0) {
+                        if (tmp.startsWith(contexts[pos].name)) {
+                            length = contexts[pos].name.length();
+                            if (tmp.length() == length) {
+                                found = true;
+                                break;
+                            } else if (tmp.startsWithIgnoreCase("/", length)) {
+                                found = true;
+                                break;
+                            }
+                        }
+                        if (lastSlash == -1) {
+                            lastSlash = tmp.nthSlash(nesting + 1);
+                        } else {
+                            lastSlash = tmp.lastIndexOf('/');
+                        }
+                        tmp.delete(lastSlash);
+                        pos = find(contexts, tmp);
+                    }
+
+                    if (!found) {
+                        if (contexts[0].name.equals("")) {
+                            context = contexts[0];
+                        }
+                    } else {
+                        context = contexts[pos];
+                    }
+                }
+            }
+
+            if (context != null) {
+                mappingData.context = context.object;
+                mappingData.contextPath.set(context.name);
+            }
+        }
+
+        // Wrapper mapping
+        if ((context != null) && (mappingData.getServiceObject() == null)) {
+            internalMapWrapper(context, uri, mappingData);
+        }
+
+    }
+
+
+    /**
+     * Wrapper mapping, using servlet rules.
+     */
+    protected final void internalMapWrapper(
+            BaseMapper.Context context,
+            CBuffer url,
+            MappingData mappingData)
+                throws Exception {
+
+        boolean noServletPath = false;
+        if (url.length() < context.name.length()) {
+            throw new IOException("Invalid mapping " + context.name + " " +
+                    url);
+        }
+
+        try {
+            // Set the servlet path.
+            mappingData.tmpServletPath.set(url,
+                    context.name.length(),
+                    url.length() - context.name.length());
+
+            if (mappingData.tmpServletPath.length() == 0) {
+                mappingData.tmpServletPath.append('/');
+                // This is just the context /example or /
+                if (!context.name.equals("/")) {
+                    noServletPath = true;
+                }
+            }
+
+            mapAfterContext(context, url, mappingData.tmpServletPath, mappingData,
+                    noServletPath);
+        } catch (ArrayIndexOutOfBoundsException ex) {
+            System.err.println(1);
+        }
+    }
+
+    void mapAfterContext(BaseMapper.Context context,
+            CBuffer url, CBuffer urlNoContext,
+            MappingData mappingData, boolean noServletPath)
+        throws Exception {
+
+
+        // Rule 1 -- Exact Match
+        BaseMapper.ServiceMapping[] exactWrappers = context.exactWrappers;
+        internalMapExactWrapper(exactWrappers, urlNoContext, mappingData);
+
+        // Rule 2 -- Prefix Match
+        boolean checkJspWelcomeFiles = false;
+        BaseMapper.ServiceMapping[] wildcardWrappers = context.wildcardWrappers;
+        if (mappingData.getServiceObject() == null) {
+
+            internalMapWildcardWrapper(wildcardWrappers, context.nesting,
+                                       urlNoContext, mappingData);
+
+            if (mappingData.getServiceObject() != null
+                    && mappingData.service.jspWildCard) {
+                if (urlNoContext.lastChar() == '/') {
+                    /*
+                     * Path ending in '/' was mapped to JSP servlet based on
+                     * wildcard match (e.g., as specified in url-pattern of a
+                     * jsp-property-group.
+                     * Force the context's welcome files, which are interpreted
+                     * as JSP files (since they match the url-pattern), to be
+                     * considered. See Bugzilla 27664.
+                     */
+                    mappingData.service = null;
+                    checkJspWelcomeFiles = true;
+                } else {
+                    // See Bugzilla 27704
+                    mappingData.wrapperPath.set(urlNoContext);
+                    mappingData.pathInfo.recycle();
+                }
+            }
+        }
+
+        if(mappingData.getServiceObject() == null && noServletPath) {
+            // The path is empty, redirect to "/"
+            mappingData.redirectPath.set(context.name);
+            mappingData.redirectPath.append("/");
+            return;
+        }
+
+        // Rule 3 -- Extension Match
+        BaseMapper.ServiceMapping[] extensionWrappers = context.extensionWrappers;
+        if (mappingData.getServiceObject() == null && !checkJspWelcomeFiles) {
+            internalMapExtensionWrapper(extensionWrappers, urlNoContext, mappingData);
+        }
+
+        // Rule 4 -- Welcome resources processing for servlets
+        if (mappingData.getServiceObject() == null) {
+            boolean checkWelcomeFiles = checkJspWelcomeFiles;
+            if (!checkWelcomeFiles) {
+                checkWelcomeFiles = (urlNoContext.lastChar() == '/');
+            }
+            if (checkWelcomeFiles) {
+                for (int i = 0; (i < context.welcomeResources.length)
+                         && (mappingData.getServiceObject() == null); i++) {
+
+                    CBuffer wpath = mappingData.tmpWelcome;
+                    wpath.set(urlNoContext);
+                    wpath.append(context.welcomeResources[i]);
+
+                    // Rule 4a -- Welcome resources processing for exact macth
+                    internalMapExactWrapper(exactWrappers, urlNoContext, mappingData);
+
+                    // Rule 4b -- Welcome resources processing for prefix match
+                    if (mappingData.getServiceObject() == null) {
+                        internalMapWildcardWrapper
+                            (wildcardWrappers, context.nesting,
+                             urlNoContext, mappingData);
+                    }
+
+                    // Rule 4c -- Welcome resources processing
+                    //            for physical folder
+                    if (mappingData.getServiceObject() == null
+                        && context.resources != null) {
+                        String pathStr = urlNoContext.toString();
+
+                        mapWelcomResource(context, urlNoContext, mappingData,
+                                extensionWrappers, pathStr);
+
+                    }
+                }
+            }
+
+        }
+
+
+        // Rule 7 -- Default servlet
+        if (mappingData.getServiceObject() == null && !checkJspWelcomeFiles) {
+            if (context.defaultWrapper != null) {
+                mappingData.service = context.defaultWrapper;
+                mappingData.requestPath.set(urlNoContext);
+                mappingData.wrapperPath.set(urlNoContext);
+            }
+            // Redirection to a folder
+            if (context.resources != null && urlNoContext.lastChar() != '/') {
+                String pathStr = urlNoContext.toString();
+                mapDefaultServlet(context, urlNoContext, mappingData,
+                        url,
+                        pathStr);
+            }
+        }
+    }
+
+    /**
+     * Filesystem-dependent method:
+     *  if pathStr corresponds to a directory, we'll need to redirect with /
+     *  at end.
+     */
+    protected void mapDefaultServlet(BaseMapper.Context context,
+            CBuffer path,
+            MappingData mappingData,
+            CBuffer url,
+            String pathStr) throws IOException {
+
+        if (context.resources != null
+                && context.resources.isDirectory(pathStr)) {
+            mappingData.redirectPath.set(url);
+            mappingData.redirectPath.append("/");
+        } else {
+            mappingData.requestPath.set(pathStr);
+            mappingData.wrapperPath.set(pathStr);
+        }
+    }
+
+
+    /**
+     * Filesystem dependent method:
+     *  check if a resource exists in filesystem.
+     */
+    protected void mapWelcomResource(BaseMapper.Context context, CBuffer path,
+                               MappingData mappingData,
+                               BaseMapper.ServiceMapping[] extensionWrappers, String pathStr) {
+
+        if (context.resources != null &&
+                context.resources.isFile(pathStr)) {
+            internalMapExtensionWrapper(extensionWrappers,
+                                        path, mappingData);
+            if (mappingData.getServiceObject() == null
+                && context.defaultWrapper != null) {
+                mappingData.service = context.defaultWrapper;
+                mappingData.requestPath.set(path);
+                mappingData.wrapperPath.set(path);
+                mappingData.requestPath.set(pathStr);
+                mappingData.wrapperPath.set(pathStr);
+            }
+        }
+    }
+
+    /**
+     * Exact mapping.
+     */
+    private final void internalMapExactWrapper
+        (BaseMapper.ServiceMapping[] wrappers, CBuffer path, MappingData mappingData) {
+        int pos = find(wrappers, path);
+        if ((pos != -1) && (path.equals(wrappers[pos].name))) {
+            mappingData.requestPath.set(wrappers[pos].name);
+            mappingData.wrapperPath.set(wrappers[pos].name);
+            mappingData.service = wrappers[pos];
+        }
+    }
+
+
+    /**
+     * Prefix mapping. ( /foo/* )
+     */
+    private final void internalMapWildcardWrapper
+        (BaseMapper.ServiceMapping[] wrappers, int nesting, CBuffer path,
+         MappingData mappingData) {
+
+        int lastSlash = -1;
+        int length = -1;
+
+        CBuffer tmp = mappingData.tmpPrefix;
+        tmp.wrap(path, 0, path.length());
+
+        int pos = find(wrappers, tmp);
+        if (pos != -1) {
+            boolean found = false;
+            while (pos >= 0) {
+                if (tmp.startsWith(wrappers[pos].name)) {
+                    length = wrappers[pos].name.length();
+                    if (tmp.length() == length) {
+                        found = true;
+                        break;
+                    } else if (tmp.startsWithIgnoreCase("/", length)) {
+                        found = true;
+                        break;
+                    }
+                }
+                if (lastSlash == -1) {
+                    lastSlash = tmp.nthSlash(nesting + 1);
+                } else {
+                    lastSlash = tmp.lastIndexOf('/');
+                }
+                tmp.delete(lastSlash);
+                pos = find(wrappers, tmp);
+            }
+            if (found) {
+                mappingData.wrapperPath.set(wrappers[pos].name);
+
+                if (path.length() > length) {
+                    mappingData.pathInfo.set
+                        (path, length, path.length() - length);
+                }
+                mappingData.requestPath.set(path);
+
+                mappingData.service = wrappers[pos];
+            }
+        }
+    }
+
+
+    /**
+     * Extension mappings.
+     */
+    protected final void internalMapExtensionWrapper
+        (BaseMapper.ServiceMapping[] wrappers, CBuffer path, MappingData mappingData) {
+
+        int dot = path.getExtension(mappingData.ext, '/', '.');
+        if (dot >= 0) {
+            int pos = find(wrappers, mappingData.ext);
+
+            if ((pos != -1)
+                    && (mappingData.ext.equals(wrappers[pos].name))) {
+
+                mappingData.wrapperPath.set(path);
+                mappingData.requestPath.set(path);
+
+                mappingData.service = wrappers[pos];
+            }
+        }
+    }
+
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(BaseMapper.Mapping[] map, CBuffer name) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+
+        if (name.compare(map[0].name) < 0 ) {
+            return -1;
+        }
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = name.compare(map[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = name.compare(map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Find a map elemnt given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int findIgnoreCase(BaseMapper.Mapping[] map,
+            CBuffer name) {
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+        if (name.compareIgnoreCase(map[0].name) < 0 ) {
+            return -1;
+        }
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = name.compareIgnoreCase(map[i].name);
+            if (result == 1) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = name.compareIgnoreCase(map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Find a map element given its name in a sorted array of map elements.
+     * This will return the index for the closest inferior or equal item in the
+     * given array.
+     */
+    private static final int find(BaseMapper.Mapping[] map, String name) {
+
+        int a = 0;
+        int b = map.length - 1;
+
+        // Special cases: -1 and 0
+        if (b == -1) {
+            return -1;
+        }
+
+        if (name.compareTo(map[0].name) < 0) {
+            return -1;
+        }
+        if (b == 0) {
+            return 0;
+        }
+
+        int i = 0;
+        while (true) {
+            i = (b + a) / 2;
+            int result = name.compareTo(map[i].name);
+            if (result > 0) {
+                a = i;
+            } else if (result == 0) {
+                return i;
+            } else {
+                b = i;
+            }
+            if ((b - a) == 1) {
+                int result2 = name.compareTo(map[b].name);
+                if (result2 < 0) {
+                    return a;
+                } else {
+                    return b;
+                }
+            }
+        }
+
+    }
+
+
+    /**
+     * Return the slash count in a given string.
+     */
+    private static final int slashCount(String name) {
+        int pos = -1;
+        int count = 0;
+        while ((pos = name.indexOf('/', pos + 1)) != -1) {
+            count++;
+        }
+        return count;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array, and prevent
+     * duplicates.
+     */
+    private static final boolean insertMap
+        (BaseMapper.Mapping[] oldMap, BaseMapper.Mapping[] newMap, BaseMapper.Mapping newElement) {
+        int pos = find(oldMap, newElement.name);
+        if ((pos != -1) && (newElement.name.equals(oldMap[pos].name))) {
+            return false;
+        }
+        System.arraycopy(oldMap, 0, newMap, 0, pos + 1);
+        newMap[pos + 1] = newElement;
+        System.arraycopy
+            (oldMap, pos + 1, newMap, pos + 2, oldMap.length - pos - 1);
+        return true;
+    }
+
+
+    /**
+     * Insert into the right place in a sorted MapElement array.
+     */
+    private static final boolean removeMap
+        (BaseMapper.Mapping[] oldMap, BaseMapper.Mapping[] newMap, String name) {
+        int pos = find(oldMap, name);
+        if ((pos != -1) && (name.equals(oldMap[pos].name))) {
+            System.arraycopy(oldMap, 0, newMap, 0, pos);
+            System.arraycopy(oldMap, pos + 1, newMap, pos,
+                             oldMap.length - pos - 1);
+            return true;
+        }
+        return false;
+    }
+
+
+    // ------------------------------------------------- MapElement Inner Class
+
+
+    protected static final class Host
+        extends BaseMapper.Mapping {
+        //Map<String, Context> contexts = new HashMap();
+        //Context rootContext;
+
+        public ContextList contextList = null;
+
+    }
+
+
+    // ------------------------------------------------ ContextList Inner Class
+
+    // Shared among host aliases.
+    protected static final class ContextList {
+
+        public BaseMapper.Context[] contexts = new BaseMapper.Context[0];
+        public int nesting = 0;
+
+    }
+
+
+    public static final class Context extends BaseMapper.Mapping {
+
+        Context(BaseMapper mapper) {
+            this.mapper = mapper;
+        }
+        public BaseMapper mapper;
+        public String[] welcomeResources = new String[0];
+        public FileConnector resources = null;
+
+        public BaseMapper.ServiceMapping defaultWrapper = null;
+
+        public BaseMapper.ServiceMapping[] exactWrappers = new BaseMapper.ServiceMapping[0];
+        public BaseMapper.ServiceMapping[] wildcardWrappers = new BaseMapper.ServiceMapping[0];
+        public BaseMapper.ServiceMapping[] extensionWrappers = new BaseMapper.ServiceMapping[0];
+        public int nesting = 0;
+
+        public void addWrapper(String path, HttpService service) {
+            mapper.addWrapper(this, path, service);
+        }
+
+    }
+
+
+    public static class ServiceMapping extends BaseMapper.Mapping {
+        public boolean jspWildCard = false;
+        // If set, the service will run in the selector thread ( should
+        // be non-blocking )
+        public boolean selectorThread = false;
+
+    }
+
+
+    protected static abstract class Mapping {
+        public String name = null;
+        public Object object = null;
+
+        public String toString() {
+            if (name == null || "".equals(name)) {
+                return "DEFAULT";
+            }
+            return name;
+        }
+    }
+
+
+    // ---------------------------------------------------- Context Inner Class
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java
new file mode 100644
index 0000000..ecf5851
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/CompressFilter.java
@@ -0,0 +1,225 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.IOBuffer;
+
+import com.jcraft.jzlib.JZlib;
+import com.jcraft.jzlib.ZStream;
+
+public class CompressFilter {
+
+    // Stream format: RFC1950
+    // 1CMF 1FLG [4DICTID] DATA 4ADLER
+    // CMF:  CINFO + CM (compression method). == x8
+    // 78 == deflate with 32k window, i.e. max window
+
+    // FLG: 2bit level, 1 bit FDICT, 5 bit FCHECK
+    // Cx, Dx - no dict; Fx, Ex - dict ( for BEST_COMPRESSION )
+
+    // Overhead: 6 bytes without dict, 10 with dict
+    // data is encoded in blocks - there is a 'block end' marker and
+    // 'last block'.
+
+    // Flush: http://www.bolet.org/~pornin/deflate-flush.html
+    // inflater needs about 9 bits
+    // Z_SYNC_FLUSH: send empty block, 00 00 FF FF - seems recomended
+    // PPP can skip this - there is a record format on top
+    // Z_PARTIAL_FLUSH: standard for SSH
+
+    ZStream cStream;
+    ZStream dStream;
+
+    byte[] dict;
+    long dictId;
+
+    public CompressFilter() {
+    }
+
+    public void recycle() {
+        if (cStream == null) {
+            return;
+        }
+        cStream.free();
+        cStream = null;
+        dStream.free();
+        dStream = null;
+    }
+
+    public void init() {
+        if (cStream != null) {
+            return;
+        }
+        // can't call: cStream.free(); - will kill the adler, NPE
+        cStream = new ZStream();
+        // BEST_COMRESSION results in 256Kb per Deflate
+        // 15 == default = 32k window
+        cStream.deflateInit(JZlib.Z_BEST_SPEED, 10);
+
+        dStream = new ZStream();
+        dStream.inflateInit();
+
+    }
+
+    CompressFilter setDictionary(byte[] dict, long id) {
+        init();
+        this.dict = dict;
+        this.dictId = id;
+        cStream.deflateSetDictionary(dict, dict.length);
+        return this;
+    }
+
+    void compress(IOBuffer in, IOBuffer out) throws IOException {
+        init();
+        BBucket bb = in.popFirst();
+
+        while (bb != null) {
+            // TODO: only the last one needs flush
+
+            // TODO: size missmatches ?
+            compress(bb, out, false);
+            bb = in.popFirst();
+        }
+
+        if (in.isClosedAndEmpty()) {
+            compressEnd(out);
+        }
+    }
+
+    void compress(BBucket bb, IOBuffer out, boolean last) throws IOException {
+        // TODO: only the last one needs flush
+
+        // TODO: size missmatches ?
+        init();
+        int flush = JZlib.Z_PARTIAL_FLUSH;
+
+        cStream.next_in = bb.array();
+        cStream.next_in_index = bb.position();
+        cStream.avail_in = bb.remaining();
+
+        while (true) {
+            ByteBuffer outB = out.getWriteBuffer();
+            cStream.next_out = outB.array();
+            cStream.next_out_index = outB.position();
+            cStream.avail_out = outB.remaining();
+
+            int err = cStream.deflate(flush);
+            check(err, cStream);
+            outB.position(cStream.next_out_index);
+            out.releaseWriteBuffer(1);
+            if (cStream.avail_out > 0 || cStream.avail_in == 0) {
+                break;
+            }
+        }
+
+        if (last) {
+            compressEnd(out);
+        }
+    }
+
+    private void compressEnd(IOBuffer out) throws IOException {
+        while (true) {
+            ByteBuffer outB = out.getWriteBuffer();
+            cStream.next_out = outB.array();
+
+            cStream.next_out_index = outB.position();
+            cStream.avail_out = outB.remaining();
+            cStream.deflate(JZlib.Z_FINISH);
+            cStream.deflateEnd();
+
+            outB.position(cStream.next_out_index);
+            out.releaseWriteBuffer(1);
+            if (cStream.avail_out > 0) {
+                break;
+            }
+        }
+    }
+
+    void decompress(IOBuffer in, IOBuffer out) throws IOException {
+        decompress(in, out, in.available());
+    }
+
+    void decompress(IOBuffer in, IOBuffer out, int len) throws IOException {
+        init();
+        BBucket bb = in.peekFirst();
+
+        while (bb != null && len > 0) {
+            dStream.next_in = bb.array();
+            dStream.next_in_index = bb.position();
+            int rd = Math.min(bb.remaining(), len);
+            dStream.avail_in = rd;
+
+            while (true) {
+                ByteBuffer outB = out.getWriteBuffer();
+
+                dStream.next_out = outB.array();
+                dStream.next_out_index = outB.position();
+                dStream.avail_out = outB.remaining();
+
+                int err = dStream.inflate(JZlib.Z_SYNC_FLUSH);
+                if (err == JZlib.Z_NEED_DICT && dict != null) {
+                    // dStream.adler has the dict id - not sure how to check
+                    if (dictId != 0 && dStream.adler != dictId) {
+                        throw new IOException("Invalid dictionary");
+                    }
+                    if (dictId == 0) {
+                        // initDict should pass a real dict id.
+                        System.err.println("Missing dict ID: " + dStream.adler);
+                    }
+                    dStream.inflateSetDictionary(dict, dict.length);
+                    err = dStream.inflate(JZlib.Z_SYNC_FLUSH);
+                }
+                outB.position(dStream.next_out_index);
+                out.releaseWriteBuffer(1);
+
+                if (err == JZlib.Z_STREAM_END) {
+                    err = dStream.inflateEnd();
+                    out.close();
+                    check(err, dStream);
+                    // move in back, not consummed
+                    bb.position(dStream.next_in_index);
+                    return;
+                }
+                check(err, dStream);
+
+                if (dStream.avail_out > 0 || dStream.avail_in == 0) {
+                    break;
+                }
+            }
+
+            in.advance(rd); // consummed
+            len -= rd;
+            bb = in.peekFirst();
+        }
+
+        if (in.isClosedAndEmpty()) {
+            // Shouldn't happen - input was not properly closed..
+            // This should throw an exception, inflateEnd will check the CRC
+            int err = dStream.inflateEnd();
+            out.close();
+            check(err, dStream);
+            out.close();
+        }
+    }
+
+    private void check(int err, ZStream stream) throws IOException {
+        if (err != JZlib.Z_OK) {
+            throw new IOException(err + " " + stream.msg);
+        }
+    }
+
+    boolean isCompressed(HttpMessage http) {
+        return false;
+    }
+
+    boolean needsCompression(HttpMessage in, HttpMessage out) {
+        return false;
+    }
+
+
+}
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java
new file mode 100644
index 0000000..31c6b9c
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ContentType.java
@@ -0,0 +1,96 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+
+/**
+ * Usefull methods for Content-Type processing
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author James Todd [gonzo@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ * @author Harish Prabandham
+ * @author costin@eng.sun.com
+ */
+public class ContentType {
+
+    /**
+     * Parse the character encoding from the specified content type header.
+     * If the content type is null, or there is no explicit character encoding,
+     * <code>null</code> is returned.
+     *
+     * @param contentType a content type header
+     */
+    public static String getCharsetFromContentType(String contentType) {
+
+        if (contentType == null)
+            return (null);
+        int start = contentType.indexOf("charset=");
+        if (start < 0)
+            return (null);
+        String encoding = contentType.substring(start + 8);
+        int end = encoding.indexOf(';');
+        if (end >= 0)
+            encoding = encoding.substring(0, end);
+        encoding = encoding.trim();
+        if ((encoding.length() > 2) && (encoding.startsWith("\""))
+            && (encoding.endsWith("\"")))
+            encoding = encoding.substring(1, encoding.length() - 1);
+        return (encoding.trim());
+
+    }
+
+
+    /**
+     * Returns true if the given content type contains a charset component,
+     * false otherwise.
+     *
+     * @param type Content type
+     * @return true if the given content type contains a charset component,
+     * false otherwise
+     */
+    public static boolean hasCharset(String type) {
+
+        boolean hasCharset = false;
+
+        int len = type.length();
+        int index = type.indexOf(';');
+        while (index != -1) {
+            index++;
+            while (index < len && Character.isWhitespace(type.charAt(index))) {
+                index++;
+            }
+            if (index+8 < len
+                    && type.charAt(index) == 'c'
+                    && type.charAt(index+1) == 'h'
+                    && type.charAt(index+2) == 'a'
+                    && type.charAt(index+3) == 'r'
+                    && type.charAt(index+4) == 's'
+                    && type.charAt(index+5) == 'e'
+                    && type.charAt(index+6) == 't'
+                    && type.charAt(index+7) == '=') {
+                hasCharset = true;
+                break;
+            }
+            index = type.indexOf(';', index);
+        }
+
+        return hasCharset;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java
new file mode 100644
index 0000000..e43b4fd
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/DefaultHttpConnector.java
@@ -0,0 +1,23 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import org.apache.tomcat.lite.io.SocketConnector;
+
+public class DefaultHttpConnector {
+
+    public synchronized static HttpConnector getNew() {
+        return new HttpConnector(new SocketConnector());
+    }
+
+    public synchronized static HttpConnector get() {
+        if (DefaultHttpConnector.socketConnector == null) {
+            socketConnector =
+                new SocketConnector();
+        }
+        return new HttpConnector(socketConnector);
+    }
+
+    private static SocketConnector socketConnector;
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java
new file mode 100644
index 0000000..51537e6
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Dispatcher.java
@@ -0,0 +1,199 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+import java.io.IOException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.FileConnector;
+
+/**
+ * This class has several functions:
+ * - maps the request to another HttpService
+ * - decide if the request should be run in the selector thread
+ * or in a thread pool
+ * - finalizes the request ( close / flush )
+ * - detects if the request is complete or set callbacks
+ * for receive/flush/done.
+ *
+ */
+public class Dispatcher implements HttpService {
+
+    private BaseMapper mapper;
+    static boolean debug = false;
+    static Logger log = Logger.getLogger("Mapper");
+    Executor tp = Executors.newCachedThreadPool();
+
+    public Dispatcher() {
+        init();
+    }
+
+    protected void init() {
+        mapper = new BaseMapper();
+    }
+
+    public void runService(HttpChannel ch) {
+        runService(ch, true);
+    }
+
+    public void runService(HttpChannel ch, boolean recycle) {
+        MappingData mapRes = ch.getRequest().getMappingData();
+        HttpService h = (HttpService) mapRes.getServiceObject();
+        try {
+            h.service(ch.getRequest(), ch.getResponse());
+            if (!ch.getRequest().isAsyncStarted()) {
+                ch.complete();
+                if (recycle) {
+                    ch.release(); // recycle objects.
+                }
+            } else {
+                // Nothing - complete must be called when done.
+            }
+        } catch (IOException e) {
+            e.printStackTrace();
+        } catch( Throwable t ) {
+            t.printStackTrace();
+        }
+    }
+
+    @Override
+    public void service(HttpRequest httpReq, HttpResponse httpRes) throws IOException {
+        service(httpReq, httpRes, false, true);
+    }
+
+    /**
+     * Process the request/response in the current thread, without
+     * release ( recycle ) at the end.
+     *
+     * For use by tests and/or in-memory running of servlets.
+     *
+     * If no connection is associated with the request - the
+     * output will remain in the out buffer.
+     */
+    public void run(HttpRequest httpReq, HttpResponse httpRes) throws IOException {
+        service(httpReq, httpRes, true, false);
+    }
+
+
+    public void service(HttpRequest httpReq, HttpResponse httpRes, boolean noThread, boolean recycle)
+            throws IOException {
+        long t0 = System.currentTimeMillis();
+        HttpChannel http = httpReq.getHttpChannel();
+
+        http.setCompletedCallback(doneCallback);
+
+        try {
+          // compute decodedURI - not done by connector
+            MappingData mapRes = httpReq.getMappingData();
+            mapRes.recycle();
+
+            mapper.map(httpReq.serverName(),
+                  httpReq.decodedURI(), mapRes);
+
+          HttpService h = (HttpService) mapRes.getServiceObject();
+
+          if (h != null) {
+              if (debug) {
+                  log.info(">>>>>>>> START: " + http.getRequest().method() + " " +
+                      http.getRequest().decodedURI() + " " +
+                      h.getClass().getSimpleName());
+              }
+
+              if (mapRes.service.selectorThread || noThread) {
+                  runService(http, recycle);
+              } else {
+                  tp.execute(httpReq.getHttpChannel().dispatcherRunnable);
+              }
+
+          } else {
+              httpRes.setStatus(404);
+              http.complete();
+          }
+
+        } catch (IOException ex) {
+            if ("Broken pipe".equals(ex.getMessage())) {
+                log.warning("Connection interrupted while writting");
+            }
+            throw ex;
+        } catch( Throwable t ) {
+            t.printStackTrace();
+            httpRes.setStatus(500);
+            http.abort(t);
+        }
+    }
+
+    private RequestCompleted doneCallback = new RequestCompleted() {
+        @Override
+        public void handle(HttpChannel client, Object extraData) throws IOException {
+            if (debug) {
+                log.info("<<<<<<<< DONE: " + client.getRequest().method() + " " +
+                        client.getRequest().decodedURI() + " " +
+                        client.getResponse().getStatus() + " "
+                        );
+            }
+        }
+    };
+
+    public BaseMapper.Context addContext(String hostname, String ctxPath,
+            Object ctx, String[] welcomeResources, FileConnector resources,
+            HttpService ctxService) {
+        return mapper.addContext(hostname, ctxPath, ctx, welcomeResources, resources,
+                ctxService);
+    }
+
+    public BaseMapper.Context addContext(String ctxPath) {
+        return mapper.addContext(null, ctxPath, null, null, null,
+                null);
+    }
+
+    public void map(CBuffer hostMB, CBuffer urlMB, MappingData md) {
+        try {
+            mapper.map(hostMB, urlMB, md);
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    public void map(BaseMapper.Context ctx,
+            CBuffer uri, MappingData md) {
+        try {
+            mapper.internalMapWrapper(ctx, uri, md);
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    public void addWrapper(BaseMapper.Context ctx, String path,
+            HttpService service) {
+        mapper.addWrapper(ctx, path, service);
+    }
+
+
+    public void setDefaultService(HttpService service) {
+        BaseMapper.Context mCtx =
+            mapper.addContext(null, "/", null, null, null, null);
+        mapper.addWrapper(mCtx, "/", service);
+    }
+
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java
new file mode 100644
index 0000000..335333b
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/Http11Connection.java
@@ -0,0 +1,1459 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.http.HttpMessage.HttpMessageBytes;
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.DumpChannel;
+import org.apache.tomcat.lite.io.FastHttpDateFormat;
+import org.apache.tomcat.lite.io.Hex;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+public class Http11Connection extends HttpConnection
+        implements IOConnector.ConnectedCallback {
+    public static final String CHUNKED = "chunked";
+
+    public static final String CLOSE = "close";
+
+    public static final String KEEPALIVE_S = "keep-alive";
+
+    public static final String CONNECTION = "connection";
+
+    public static final String TRANSFERENCODING = "transfer-encoding";
+
+
+    protected static Logger log = Logger.getLogger("Http11Connection");
+    static final byte COLON = (byte) ':';
+
+    // super.net is the socket
+
+    boolean debug;
+    BBuffer line = BBuffer.wrapper();
+    boolean endSent = false;
+
+    BodyState receiveBodyState = new BodyState();
+    BodyState sendBodyState = new BodyState();
+
+    BBuffer headW = BBuffer.wrapper();
+
+    boolean headersReceived = false;
+    boolean bodyReceived = false;
+
+    /**
+     * Close connection when done writting, no content-length/chunked,
+     * or no keep-alive ( http/1.0 ) or error.
+     *
+     * ServerMode: set if HTTP/0.9 &1.0 || !keep-alive
+     * ClientMode: not currently used
+     */
+    boolean keepAlive = true;
+
+    protected boolean http11 = true;
+    protected boolean http10 = false;
+    protected boolean http09 = false;
+
+    HttpConnection switchedProtocol = null;
+
+    private int requestCount = 0;
+
+    // dataReceived and endSendReceive
+    private Object readLock = new Object();
+
+    public Http11Connection(HttpConnector httpConnector) {
+        this.httpConnector = httpConnector;
+        if (httpConnector != null) {
+            debug = httpConnector.debugHttp;
+        }
+    }
+
+    public void beforeRequest() {
+        nextRequest();
+        headRecvBuf.recycle();
+    }
+
+    public void nextRequest() {
+        endSent = false;
+        keepAlive = true;
+        receiveBodyState.recycle();
+        sendBodyState.recycle();
+        http11 = true;
+        http09 = false;
+        http10 = false;
+        headersReceived = false;
+        bodyReceived = false;
+    }
+
+    public Http11Connection serverMode() {
+        serverMode = true;
+        return this;
+    }
+
+    private boolean readHead() throws IOException {
+        while (true) {
+            int read;
+            if (requestCount == 0 && headRecvBuf.remaining() < 4) {
+                // requests have at least 4 bytes - detect protocol
+                read = net.getIn().read(headRecvBuf, 4);
+                if (read < 0) {
+                    return closeInHead();
+                }
+                if (read < 4) {
+                    return false; // need more
+                }
+                // we have at least 4 bytes
+                if (headRecvBuf.get(0) == 0x80 &&
+                        headRecvBuf.get(1) == 0x01) {
+                    // SPDY signature ( experimental )
+                    switchedProtocol = new SpdyConnection(httpConnector,
+                            remoteHost);
+                    if (serverMode) {
+                        switchedProtocol.serverMode = true;
+                    }
+                    switchedProtocol.withExtraBuffer(headRecvBuf);
+                    // Will also call handleReceived
+                    switchedProtocol.setSink(net);
+                    return false;
+                }
+
+            }
+
+            // we know we have one
+            read = net.getIn().readLine(headRecvBuf);
+            // Remove starting empty lines.
+            headRecvBuf.skipEmptyLines();
+
+            // Do we have another full line in the input ?
+            if (BBuffer.hasLFLF(headRecvBuf)) {
+                break; // done
+            }
+            if (read == 0) { // no more data
+                return false;
+            }
+            if (read < 0) {
+                return closeInHead();
+            }
+        }
+
+
+        return true;
+    }
+
+    private boolean closeInHead() throws IOException {
+        if (debug) {
+            trace("CLOSE while reading HEAD");
+        }
+        // too early - we don't have the head
+        abort("Close in head");
+        return false;
+    }
+
+    // Unit tests use this to access the HttpChannel
+    protected HttpChannel checkHttpChannel() throws IOException {
+        if (switchedProtocol != null) {
+            return switchedProtocol.checkHttpChannel();
+        }
+        if (activeHttp == null) {
+            if (serverMode) {
+                activeHttp = httpConnector.getServer();
+                activeHttp.setConnection(this);
+                if (httpConnector.defaultService != null) {
+                    activeHttp.setHttpService(httpConnector.defaultService);
+                }
+            } else {
+            }
+        }
+        return activeHttp;
+    }
+
+    @Override
+    public void dataReceived(IOBuffer netx) throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.dataReceived(netx);
+            return;
+        }
+        //trace("handleReceived " + headersReceived);
+        if (!checkKeepAliveClient()) {
+            return; // we were in client keep alive mode
+        }
+        // endSendReceived uses same lock - it will call this
+        // to check outstanding bytes
+        synchronized (readLock) {
+            if (bodyReceived) {
+                return; // leave data in net buffer, for next req
+            }
+
+            if (!headersReceived) {
+                if (!readHead()) {
+                    return;
+                }
+            }
+
+            // We have a header
+            if (activeHttp == null) {
+                if (checkHttpChannel() == null) {
+                    return;
+                }
+            }
+
+            IOBuffer receiveBody = activeHttp.receiveBody;
+
+            if (!headersReceived) {
+                headRecvBuf.wrapTo(headW);
+                parseMessage(activeHttp, headW);
+                // Part of parseMessage we can switch the protocol
+                if (switchedProtocol != null) {
+                    return;
+                }
+
+                if (serverMode && activeHttp.httpReq.decodedUri.remaining() == 0) {
+                    abort(activeHttp, "Invalid url");
+                }
+
+                headersReceived = true;
+                // Send header callbacks - we process any incoming data
+                // first, so callbacks have more info
+                trace("Send headers received callback " + activeHttp.httpService);
+                activeHttp.handleHeadersReceived(activeHttp.inMessage);
+            }
+
+            // any remaining data will be processed as part of the
+            // body - or left in the channel until endSendReceive()
+
+            if (!bodyReceived) {
+                // Will close receiveBody when it consummed enough
+                rawDataReceived(activeHttp, receiveBody, net.getIn());
+                // Did we process anything ?
+                if (receiveBody.getBufferCount() > 0) {
+                    activeHttp.sendHandleReceivedCallback(); // callback
+                }
+            }
+            // Receive has marked the body as closed
+            if (receiveBody.isAppendClosed()) {
+                bodyReceived = true;
+                activeHttp.handleEndReceive();
+            }
+
+
+            if (net.getIn().isClosedAndEmpty()) {
+                // If not already closed.
+                closeStreamOnEnd("closed after body");
+            }
+
+        }
+    }
+
+    /**
+     * We got data while in client keep alive ( no activeHttp )
+     *
+     * @return false if there is an error
+     */
+    private boolean checkKeepAliveClient() throws IOException {
+        // Client, no active connection ( keep alive )
+        if (!serverMode && activeHttp == null) {
+            if (net.getIn().isClosedAndEmpty() || !net.isOpen()) {
+                // server disconnected, fine
+                httpConnector.cpool.stopKeepAlive(this);
+                return false;
+            }
+            if (net.getIn().available() == 0) {
+                return true;
+            }
+            log.warning("Unexpected message from server in client keep alive "
+                    + net.getIn() + ": " + net.getIn().readAll(null));
+            if (net.isOpen()) {
+                net.close();
+            }
+            return false;
+        }
+        return true;
+    }
+
+    private void processProtocol(CBuffer protocolMB) throws IOException {
+        http11 = false;
+        http09 = false;
+        http10 = false;
+
+        if (protocolMB.equals(HttpChannel.HTTP_11)) {
+            http11 = true;
+        } else if (protocolMB.equals(HttpChannel.HTTP_10)) {
+            http10 = true;
+        } else if (protocolMB.equals("")) {
+            http09 = true;
+        } else {
+            http11 = true; // hopefully will be backward compat
+        }
+    }
+
+    void closeStreamOnEnd(String cause) {
+        if (debug) {
+            log.info("Not reusing connection because: " + cause);
+        }
+        keepAlive = false;
+    }
+
+    boolean keepAlive() {
+        if (httpConnector != null) {
+            if (serverMode && !httpConnector.serverKeepAlive) {
+                keepAlive = false;
+            }
+            if (!serverMode && !httpConnector.clientKeepAlive) {
+                keepAlive = false;
+            }
+        }
+        if (http09) {
+            keepAlive = false;
+        }
+        if (net != null && !net.isOpen()) {
+            keepAlive = false;
+        }
+        return keepAlive;
+    }
+
+    @Override
+    protected void endSendReceive(HttpChannel http) throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.endSendReceive(http);
+            return;
+        }
+        chunk.recycle();
+        rchunk.recycle();
+        boolean keepAlive = keepAlive();
+        if (!keepAlive) {
+            if (debug) {
+                log.info("--- Close socket, no keepalive " + net);
+            }
+            if (net != null) {
+                net.close();
+                net.startSending();
+
+            }
+        }
+
+        requestCount++;
+        beforeRequest();
+        httpConnector.cpool.afterRequest(http, this, true);
+
+        if (serverMode && keepAlive) {
+            handleReceived(net); // will attempt to read next req
+        }
+    }
+
+    private void trace(String s) {
+        if(debug) {
+            log.info(this.toString() + " " + activeHttp + " " + s);
+        }
+    }
+
+    private boolean isDone(BodyState bodys, IOBuffer body) {
+        if (bodys.noBody) {
+            return true;
+        }
+        if (bodys.isContentDelimited()) {
+            if (!bodys.chunked && bodys.remaining == 0) {
+                return true;
+            } else if (bodys.chunked && body.isAppendClosed()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    void parseMessage(HttpChannel http, BBuffer headB) throws IOException {
+        //Parse the response
+        line.recycle();
+        headB.readLine(line);
+
+        HttpMessageBytes msgBytes;
+
+        if (serverMode) {
+            msgBytes = http.httpReq.getMsgBytes();
+            parseRequestLine(line, msgBytes.method(),
+                    msgBytes.url(),
+                    msgBytes.query(),
+                    msgBytes.protocol());
+        } else {
+            msgBytes = http.httpRes.getMsgBytes();
+            parseResponseLine(line, msgBytes.protocol(),
+                    msgBytes.status(), msgBytes.message());
+        }
+
+        parseHeaders(http, msgBytes, headB);
+
+        http.inMessage.state = HttpMessage.State.BODY_DATA;
+
+        http.inMessage.processReceivedHeaders();
+
+        // TODO: hook to allow specific charsets ( can be done later )
+        processProtocol(http.inMessage.protocol());
+
+        if (serverMode) {
+            // requested connection:close/keepAlive and proto
+            updateKeepAlive(http.getRequest().getMimeHeaders(), true);
+
+            processExpectation(http);
+
+            processContentDelimitation(receiveBodyState, http.getRequest());
+            // Spec:
+            // The presence of a message-body in a request is signaled by the
+            // inclusion of a Content-Length or Transfer-Encoding header field in
+            // the request's message-headers
+            // Server should read - but ignore ..
+            receiveBodyState.noBody = !receiveBodyState.isContentDelimited();
+
+            updateCloseOnEnd(receiveBodyState, http, http.receiveBody);
+
+            /*
+             * The presence of a message-body in a request is signaled by the
+             * inclusion of a Content-Length or Transfer-Encoding header field in
+             * the request's message-headers. A message-body MUST NOT be included
+             * in a request if the specification of the request method
+             * (section 5.1.1) does not allow sending an entity-body in requests.
+             * A server SHOULD read and forward a message-body on any request; if the request method does not include defined semantics for an entity-body, then the message-body SHOULD be ignored when handling the request.
+             */
+            if (!receiveBodyState.isContentDelimited()) {
+                // No body
+                http.getIn().close();
+            }
+
+        } else {
+            receiveBodyState.noBody = !http.getResponse().hasBody();
+
+            updateKeepAlive(http.getResponse().getMimeHeaders(), false);
+
+            if (statusDropsConnection(http.getResponse().getStatus())) {
+                closeStreamOnEnd("response status drops connection");
+            }
+            IOBuffer body = http.receiveBody;
+            processContentDelimitation(receiveBodyState, http.getResponse());
+
+            if (isDone(receiveBodyState, body)) {
+                body.close();
+            }
+
+            if (!receiveBodyState.isContentDelimited()) {
+                closeStreamOnEnd("not content delimited");
+            }
+        }
+
+    }
+
+    private void processExpectation(HttpChannel http) throws IOException {
+        http.expectation = false;
+        MultiMap headers = http.getRequest().getMimeHeaders();
+
+        CBuffer expect = headers.getHeader("expect");
+        if ((expect != null)
+                && (expect.indexOf("100-continue") != -1)) {
+            http.expectation = true;
+
+            // TODO: configure, use the callback or the servlet 'read'.
+            net.getOut().append("HTTP/1.1 100 Continue\r\n\r\n");
+            net.startSending();
+        }
+    }
+
+
+    /**
+     * Updates chunked, contentLength, remaining - based
+     * on headers
+     */
+    private void processContentDelimitation(BodyState bodys,
+            HttpMessage httpMsg) {
+
+        bodys.contentLength = httpMsg.getContentLength();
+        if (bodys.contentLength >= 0) {
+            bodys.remaining = bodys.contentLength;
+        }
+
+        // TODO: multiple transfer encoding headers, only process the last
+        String transferEncodingValue = httpMsg.getHeader(TRANSFERENCODING);
+        if (transferEncodingValue != null) {
+            int startPos = 0;
+            int commaPos = transferEncodingValue.indexOf(',');
+            String encodingName = null;
+            while (commaPos != -1) {
+                encodingName = transferEncodingValue.substring
+                (startPos, commaPos).toLowerCase().trim();
+                if ("chunked".equalsIgnoreCase(encodingName)) {
+                    bodys.chunked = true;
+                }
+                startPos = commaPos + 1;
+                commaPos = transferEncodingValue.indexOf(',', startPos);
+            }
+            encodingName = transferEncodingValue.substring(startPos)
+                .toLowerCase().trim();
+            if ("chunked".equals(encodingName)) {
+                bodys.chunked = true;
+                httpMsg.chunked = true;
+            } else {
+                System.err.println("TODO: ABORT 501");
+                //return 501; // Currently only chunked is supported for
+                // transfer encoding.
+            }
+        }
+
+        if (bodys.chunked) {
+            bodys.remaining = 0;
+        }
+    }
+
+    /**
+     * Read the request line. This function is meant to be used during the
+     * HTTP request header parsing. Do NOT attempt to read the request body
+     * using it.
+     *
+     * @throws IOException If an exception occurs during the underlying socket
+     * read operations, or if the given buffer is not big enough to accomodate
+     * the whole line.
+     */
+    boolean parseRequestLine(BBuffer line,
+            BBuffer methodMB, BBuffer requestURIMB,
+            BBuffer queryMB,
+            BBuffer protoMB)
+        throws IOException {
+
+        line.readToSpace(methodMB);
+        line.skipSpace();
+
+        line.readToDelimOrSpace(HttpChannel.QUESTION, requestURIMB);
+        if (line.remaining() > 0 && line.get(0) == HttpChannel.QUESTION) {
+            // Has query
+            line.readToSpace(queryMB);
+            // don't include '?'
+            queryMB.position(queryMB.position() + 1);
+        } else {
+            queryMB.setBytes(line.array(), line.position(), 0);
+        }
+        line.skipSpace();
+
+        line.readToSpace(protoMB);
+
+        // proto is optional ( for 0.9 )
+        return requestURIMB.remaining() > 0;
+    }
+
+    boolean parseResponseLine(BBuffer line,
+            BBuffer protoMB, BBuffer statusCode, BBuffer status)
+            throws IOException {
+        line.skipEmptyLines();
+
+        line.readToSpace(protoMB);
+        line.skipSpace();
+        line.readToSpace(statusCode);
+        line.skipSpace();
+        line.wrapTo(status);
+
+        // message may be empty
+        return statusCode.remaining() > 0;
+    }
+
+    List<String> connectionHeaders = new ArrayList<String>();
+
+    private void parseHeaders(HttpChannel http, HttpMessageBytes msgBytes,
+            BBuffer head)
+                throws IOException {
+
+        head.readLine(line);
+
+        int idx = 0;
+
+        BBuffer upgrade = null;
+
+        while(line.remaining() > 0) {
+            // not empty..
+            idx = msgBytes.addHeader();
+            BBuffer nameBuf = msgBytes.getHeaderName(idx);
+            BBuffer valBuf = msgBytes.getHeaderValue(idx);
+            parseHeader(http, head, line, nameBuf, valBuf);
+
+            // TODO: process 'interesting' headers here.
+            if (nameBuf.equalsIgnoreCase("connection")) {
+                // TODO: save and remove if not recognized
+            }
+            if (nameBuf.equalsIgnoreCase("upgrade")) {
+                upgrade = valBuf;
+            }
+        }
+
+        if (upgrade != null) {
+            if (upgrade.equalsIgnoreCase("WebSocket")) {
+
+            } else if (upgrade.equalsIgnoreCase("SPDY/1.0")) {
+
+            }
+        }
+
+        // TODO: process connection headers
+    }
+
+    /**
+     * Parse one header.
+     * Line must be populated. On return line will be populated
+     * with the next header:
+     *
+     * @param line current header line, not empty.
+     */
+    int parseHeader(HttpChannel http, BBuffer head,
+            BBuffer line, BBuffer name, BBuffer value)
+          throws IOException {
+
+        int newPos = line.readToDelimOrSpace(COLON, name);
+        line.skipSpace();
+        if (line.readByte() != COLON) {
+            throw new IOException("Missing ':' in header name " + line);
+        }
+        line.skipSpace();
+        line.read(value); // remaining of the line
+
+        while (true) {
+            head.readLine(line);
+            if (line.remaining() == 0) {
+                break;
+            }
+            int first = line.get(0);
+            if (first != BBuffer.SP && first != BBuffer.HT) {
+                break;
+            }
+            // continuation line - append it to value
+            value.setEnd(line.getEnd());
+            line.position(line.limit());
+        }
+
+        // We may want to keep the original and use separate buffer ?
+        http.normalizeHeader(value);
+        return 1;
+    }
+
+    private int receiveDone(HttpChannel http, IOBuffer body, boolean frameError) throws IOException {
+        // Content-length case, we're done reading
+        body.close();
+
+        http.error = frameError;
+        if (frameError) {
+            closeStreamOnEnd("frame error");
+        }
+
+        return DONE;
+    }
+
+    /**
+     * Called when raw body data is received.
+     * Callback should not consume past the end of the body.
+     * @param rawReceiveBuffers
+     *
+     */
+    private void rawDataReceived(HttpChannel http, IOBuffer body,
+            IOBuffer rawReceiveBuffers) throws IOException {
+        // TODO: Make sure we don't process more than we need ( eat next req ).
+        // If we read too much: leave it in readBuf, the finalzation code
+        // should skip KeepAlive and start processing it.
+        // we need to read at least something - to detect -1 ( we could
+        // suspend right away, but seems safer
+        BodyState bodys = receiveBodyState;
+
+        while (http.inMessage.state == HttpMessage.State.BODY_DATA) {
+            if (receiveBodyState.noBody) {
+                receiveDone(http, body, false);
+                return;
+            }
+            if (rawReceiveBuffers.isClosedAndEmpty()) {
+                if (receiveBodyState.isContentDelimited()) {
+                    if (receiveBodyState.contentLength >= 0 && receiveBodyState.remaining == 0) {
+                        receiveDone(http, body, false);
+                    } else {
+                        // End of input - other side closed, no more data
+                        //log.info("CLOSE while reading " + this);
+                        // they're not supposed to close !
+                        receiveDone(http, body, true);
+                    }
+                } else {
+                    receiveDone(http, body, false); // ok
+                }
+                // input connection closed ?
+                closeStreamOnEnd("Closed input");
+                return;
+            }
+            BBucket rawBuf = rawReceiveBuffers.peekFirst();
+            if (rawBuf == null) {
+                return;  // need more data
+            }
+
+            if (!bodys.isContentDelimited()) {
+                while (true) {
+                    BBucket first = rawReceiveBuffers.popFirst();
+                    if (first == null) {
+                        break; // will go back to check if done.
+                    } else {
+                        received(body, first);
+                    }
+                }
+            } else {
+
+                if (bodys.contentLength >= 0 && bodys.remaining == 0) {
+                    receiveDone(http, body, false);
+                    return;
+                }
+
+                if (bodys.chunked && bodys.remaining == 0) {
+                    int rc = NEED_MORE;
+                    // TODO: simplify, use readLine()
+                    while (rc == NEED_MORE) {
+                        rc = rchunk.parseChunkHeader(rawReceiveBuffers);
+                        if (rc == ERROR) {
+                            http.abort("Chunk error");
+                            receiveDone(http, body, true);
+                            return;
+                        } else if (rc == NEED_MORE) {
+                            return;
+                        }
+                    }
+                    if (rc == 0) { // last chunk
+                        receiveDone(http, body, false);
+                        return;
+                    } else {
+                        bodys.remaining = rc;
+                    }
+                }
+
+                rawBuf = (BBucket) rawReceiveBuffers.peekFirst();
+                if (rawBuf == null) {
+                    return;  // need more data
+                }
+
+
+                if (bodys.remaining < rawBuf.remaining()) {
+                    // To buffer has more data than we need.
+                    int lenToConsume = (int) bodys.remaining;
+                    BBucket sb = rawReceiveBuffers.popLen(lenToConsume);
+                    received(body, sb);
+                    //log.info("Queue received buffer " + this + " " + lenToConsume);
+                    bodys.remaining = 0;
+                } else {
+                    BBucket first = rawReceiveBuffers.popFirst();
+                    bodys.remaining -= first.remaining();
+                    received(body, first);
+                    //log.info("Queue full received buffer " + this + " RAW: " + rawReceiveBuffers);
+                }
+                if (bodys.contentLength >= 0 && bodys.remaining == 0) {
+                    // Content-Length, all done
+                    body.close();
+                    receiveDone(http, body, false);
+                }
+            }
+        }
+    }
+
+    private void received(IOBuffer body, BBucket bb) throws IOException {
+        body.queue(bb);
+    }
+
+
+    protected void sendRequest(HttpChannel http)
+            throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.sendRequest(http);
+            return;
+        }
+
+        // Update transfer fields based on headers.
+        processProtocol(http.getRequest().protocol());
+        updateKeepAlive(http.getRequest().getMimeHeaders(), true);
+
+        // Update Host header
+        if (http.getRequest().getMimeHeaders().getHeader("Host") == null) {
+            String target = http.getTarget();
+            if (target == null) {
+                throw new IOException("Missing host header");
+            }
+            CBuffer hostH = http.getRequest().getMimeHeaders().addValue("Host");
+            if (target.endsWith(":80")) {
+                hostH.set(target.substring(0, target.length() - 3));
+            } else {
+                hostH.set(target);
+            }
+        }
+
+        processContentDelimitation(sendBodyState,
+                http.getRequest());
+
+
+        CBuffer method = http.getRequest().method();
+        if (method.equals("GET") || method.equals("HEAD")) {
+            // TODO: add the others
+            sendBodyState.noBody = true;
+        }
+
+        // 1.0: The presence of an entity body in a request is signaled by
+        // the inclusion of a Content-Length header field in the request
+        // message headers. HTTP/1.0 requests containing an entity body
+        // must include a valid Content-Length header field.
+        if (http10 && !sendBodyState.isContentDelimited()) {
+            // Will not close connection - just flush and mark the body
+            // as sent
+            sendBodyState.noBody = true;
+        }
+
+        if (sendBodyState.noBody) {
+            http.getRequest().getMimeHeaders().remove(HttpChannel.CONTENT_LENGTH);
+            http.getRequest().getMimeHeaders().remove(TRANSFERENCODING);
+            http.getOut().close();
+        } else {
+            long contentLength =
+                http.getRequest().getContentLength();
+            if (contentLength < 0) {
+                http.getRequest().getMimeHeaders().addValue("Transfer-Encoding").
+                    set(CHUNKED);
+            }
+        }
+
+        updateCloseOnEnd(sendBodyState, http, http.sendBody);
+
+        try {
+            serialize(http.getRequest(), net.getOut());
+            if (http.debug) {
+                http.trace("S: \n" + net.getOut());
+            }
+
+            if (http.outMessage.state == HttpMessage.State.HEAD) {
+                http.outMessage.state = HttpMessage.State.BODY_DATA;
+            }
+
+
+            // TODO: add any body and flush. More body can be added later -
+            // including 'end'.
+
+            http.startSending();
+        } catch (Throwable t) {
+            log.log(Level.SEVERE, "Error sending request", t);
+            abort(t.getMessage());
+        }
+
+    }
+
+
+    /**
+     * Determine if we must drop the connection because of the HTTP status
+     * code.  Use the same list of codes as Apache/httpd.
+     */
+    private boolean statusDropsConnection(int status) {
+        return status == 400 /* SC_BAD_REQUEST */ ||
+        status == 408 /* SC_REQUEST_TIMEOUT */ ||
+        status == 411 /* SC_LENGTH_REQUIRED */ ||
+        status == 413 /* SC_REQUEST_ENTITY_TOO_LARGE */ ||
+        status == 414 /* SC_REQUEST_URI_TOO_LARGE */ ||
+        status == 500 /* SC_INTERNAL_SERVER_ERROR */ ||
+        status == 503 /* SC_SERVICE_UNAVAILABLE */ ||
+        status == 501 /* SC_NOT_IMPLEMENTED */;
+    }
+
+    protected void sendResponseHeaders(HttpChannel http)
+            throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.sendResponseHeaders(http);
+            return;
+        }
+
+        if (!serverMode) {
+            throw new IOException("Only in server mode");
+        }
+        endSent = false;
+        IOBuffer sendBody = http.sendBody;
+        HttpResponse res = http.getResponse();
+        if (res.isCommitted()) {
+            return;
+        }
+        res.setCommitted(true);
+
+        sendBodyState.noBody = !res.hasBody();
+
+        if (statusDropsConnection(res.getStatus())) {
+            closeStreamOnEnd("status drops connection");
+        }
+        if (http.error) {
+            closeStreamOnEnd("error");
+        }
+
+        MultiMap headers = res.getMimeHeaders();
+
+        // Add date header
+        if (headers.getHeader("Date") == null) {
+            headers.setValue("Date").set(FastHttpDateFormat.getCurrentDate());
+        }
+
+        // Add server header
+        if (http.serverHeader.length() > 0) {
+            headers.setValue("Server").set(http.serverHeader);
+        }
+
+        // Decide on a transfer encoding for out.
+        if (keepAlive()) { // request and user allows keep alive
+            int cl = res.getContentLength();
+
+            if (http10) {
+                if (cl < 0 && !sendBodyState.noBody &&
+                        sendBody.isAppendClosed()) {
+                    // We can generate content-lenght
+                    cl = sendBody.available();
+                    res.setContentLength(cl);
+                }
+                if (cl < 0 && !sendBodyState.noBody) {
+                    closeStreamOnEnd("HTTP/1.0 without content length");
+                } else {
+                    headers.setValue(CONNECTION).set(KEEPALIVE_S);
+                }
+            } else { // http11
+                if (!sendBodyState.noBody) {
+                    if (cl < 0) {
+                        res.getMimeHeaders().setValue(TRANSFERENCODING).set(CHUNKED);
+                    }
+                }
+            }
+        } else {
+            headers.setValue(CONNECTION).set(CLOSE);
+            // since we close the connection - don't bother with
+            // transfer encoding
+            headers.remove(TRANSFERENCODING);
+        }
+
+        // Update our internal state based on headers we just set.
+        processContentDelimitation(sendBodyState, res);
+        updateCloseOnEnd(sendBodyState, http, sendBody);
+
+
+        if (http.debug) {
+            http.trace("Send response headers " + net);
+        }
+        if (net != null) {
+            serialize(res, net.getOut());
+        }
+
+        if (http.outMessage.state == HttpMessage.State.HEAD) {
+            http.outMessage.state = HttpMessage.State.BODY_DATA;
+        }
+
+        if (isDone(sendBodyState, sendBody)) {
+            http.getOut().close();
+        }
+
+        if (net != null) {
+            net.startSending();
+        }
+    }
+
+    private void abort(String t) throws IOException {
+        abort(activeHttp, t);
+    }
+
+    private void updateCloseOnEnd(BodyState bodys, HttpChannel http, IOBuffer body) {
+        if (!bodys.isContentDelimited() && !bodys.noBody) {
+            closeStreamOnEnd("not content delimited");
+        }
+    }
+
+    /**
+     * Disconnect abruptly - client closed, frame errors, etc
+     * @param t
+     * @throws IOException
+     */
+    public void abort(HttpChannel http, String t) throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.abort(http, t);
+            return;
+        }
+        keepAlive = false;
+        if (net != null ) {
+            if (net.isOpen()) {
+                net.close();
+                net.startSending();
+            }
+        }
+        if (http != null) {
+            http.abort(t);
+        }
+    }
+
+    /**
+     * Update keepAlive based on Connection header and protocol.
+     */
+    private void updateKeepAlive(MultiMap headers, boolean request) {
+        if (http09) {
+            closeStreamOnEnd("http 0.9");
+            return;
+        }
+
+        // TODO: also need to remove headers matching connection
+        // ( like 'upgrade')
+
+        CBuffer value = headers.getHeader(CONNECTION);
+        // TODO: split it by space
+        if (value != null) {
+            value.toLower();
+            if (value.indexOf(CLOSE) >= 0) {
+                // 1.1 ( but we accept it for 1.0 too )
+                closeStreamOnEnd("connection close");
+            }
+            if (http10 && value.indexOf(KEEPALIVE_S) < 0) {
+                // Keep-Alive required for http/1.0
+                closeStreamOnEnd("connection != keep alive");
+            }
+            // we have connection: keepalive, good
+        } else {
+            // no connection header - for 1.1 default is keepAlive,
+            // for 10 it's close
+            if (http10) {
+                closeStreamOnEnd("http1.0 no connection header");
+            }
+        }
+    }
+
+    @Override
+    public void startSending() throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.startSending();
+            return;
+        }
+
+    }
+
+    @Override
+    public void startSending(HttpChannel http) throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.startSending(http);
+            return;
+        }
+        http.send(); // if needed
+
+        if (net == null) {
+            return; // not connected yet.
+        }
+
+        if (net.getOut().isAppendClosed()) {
+            abort("Net closed");
+        } else {
+            flushToNext(http.sendBody, net.getOut());
+            net.startSending();
+        }
+
+    }
+
+    protected void outClosed(HttpChannel http) throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.outClosed(http);
+            return;
+        }
+        // TODO: move it ?
+        if (sendBodyState.isContentDelimited() && !http.error) {
+            if (!sendBodyState.chunked &&
+                    sendBodyState.remaining - http.getOut().available() > 0) {
+                http.abort("CLOSE CALLED WITHOUT FULL LEN");
+            }
+        }
+
+    }
+
+    @Override
+    public void handleFlushed(IOChannel net) throws IOException {
+        if (switchedProtocol != null) {
+            switchedProtocol.handleFlushed(net);
+            return;
+        }
+        if (activeHttp != null) {
+            activeHttp.flushLock.signal(this);
+            activeHttp.handleFlushed(this);
+            if (activeHttp.sendBody.isClosedAndEmpty()) {
+                activeHttp.handleEndSent();
+            }
+        }
+    }
+
+
+    private void flushToNext(IOBuffer body, IOBuffer out) throws IOException {
+
+        synchronized (this) {
+            // TODO: better head support
+            if (sendBodyState.noBody) {
+                for (int i = 0; i < body.getBufferCount(); i++) {
+                    Object bc = body.peekBucket(i);
+                    if (bc instanceof BBucket) {
+                        ((BBucket) bc).release();
+                    }
+                }
+                body.clear();
+                return;
+            }
+
+            // TODO: only send < remainingWrite, if buffer
+            // keeps changing after startWrite() is called (shouldn't)
+
+            if (sendBodyState.chunked) {
+                sendChunked(sendBodyState, body, out);
+            } else if (sendBodyState.contentLength >= 0) {
+                // content-length based
+                sendContentLen(sendBodyState, body, out);
+            } else {
+                sendCloseDelimited(body, out);
+            }
+        }
+    }
+
+    private void sendCloseDelimited(IOBuffer body, IOBuffer out) throws IOException {
+        // Close delimitation
+        while (true) {
+            Object bc = body.popFirst();
+            if (bc == null) {
+                break;
+            }
+            out.queue(bc);
+        }
+        if (body.isClosedAndEmpty()) {
+            out.close(); // no content-delimitation
+        }
+    }
+
+    /**
+     * Convert the request to bytes, ready to send.
+     */
+    public static void serialize(HttpRequest req, IOBuffer rawSendBuffers2) throws IOException {
+        rawSendBuffers2.append(req.method());
+        rawSendBuffers2.append(BBuffer.SP);
+
+        // TODO: encode or use decoded
+        rawSendBuffers2.append(req.requestURI());
+        if (req.queryString().length() > 0) {
+            rawSendBuffers2.append("?");
+            rawSendBuffers2.append(req.queryString());
+        }
+
+        rawSendBuffers2.append(BBuffer.SP);
+        rawSendBuffers2.append(req.protocol());
+        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+
+        serializeHeaders(req.getMimeHeaders(), rawSendBuffers2);
+    }
+
+    /**
+     * Convert the response to bytes, ready to send.
+     */
+    public static void serialize(HttpResponse res, IOBuffer rawSendBuffers2) throws IOException {
+
+        rawSendBuffers2.append(res.protocol()).append(' ');
+        String status = Integer.toString(res.getStatus());
+        rawSendBuffers2.append(status).append(' ');
+        if (res.getMessageBuffer().length() > 0) {
+            rawSendBuffers2.append(res.getMessage());
+        } else {
+            rawSendBuffers2
+                .append(res.getMessage(res.getStatus()));
+        }
+        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+        // Headers
+        serializeHeaders(res.getMimeHeaders(), rawSendBuffers2);
+    }
+
+    public static void serializeHeaders(MultiMap mimeHeaders, IOBuffer rawSendBuffers2) throws IOException {
+        for (int i = 0; i < mimeHeaders.size(); i++) {
+            CBuffer name = mimeHeaders.getName(i);
+            CBuffer value = mimeHeaders.getValue(i);
+            if (name.length() == 0 || value.length() == 0) {
+                continue;
+            }
+            rawSendBuffers2.append(name);
+            rawSendBuffers2.append(Http11Connection.COLON);
+            rawSendBuffers2.append(value);
+            rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+        }
+        rawSendBuffers2.append(BBuffer.CRLF_BYTES);
+    }
+
+
+    private boolean sendContentLen(BodyState bodys, IOBuffer body, IOBuffer out) throws IOException {
+        while (true) {
+            BBucket bucket = body.peekFirst();
+            if (bucket == null) {
+                break;
+            }
+            int len = bucket.remaining();
+            if (len <= bodys.remaining) {
+                bodys.remaining -= len;
+                bucket = body.popFirst();
+                out.queue(bucket);
+            } else {
+                // Write over the end of the buffer !
+                log.severe("write more than Content-Length");
+                len = (int) bodys.remaining;
+                // data between position and limit
+                bucket = body.popLen((int) bodys.remaining);
+                out.queue(bucket);
+                while (bucket != null) {
+                    bucket = body.popFirst();
+                    if (bucket != null) {
+                        bucket.release();
+                    }
+                }
+
+                // forced close
+                //close();
+                bodys.remaining = 0;
+                return true;
+            }
+        }
+        if (body.isClosedAndEmpty()) {
+            //http.rawSendBuffers.queue(IOBrigade.MARK);
+            if (bodys.remaining > 0) {
+                closeStreamOnEnd("sent more than content-length");
+                log.severe("Content-Length > body");
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean sendChunked(BodyState bodys, IOBuffer body, IOBuffer out) throws IOException {
+        int len = body.available();
+
+        if (len > 0) {
+            ByteBuffer sendChunkBuffer = chunk.prepareChunkHeader(len);
+            bodys.remaining = len;
+            out.queue(sendChunkBuffer);
+            while (bodys.remaining > 0) {
+                BBucket bc = body.popFirst();
+                bodys.remaining -= bc.remaining();
+                out.queue(bc);
+            }
+        }
+
+        if (body.isClosedAndEmpty()) {
+            synchronized(this) {
+                if (!endSent) {
+                    out.append(chunk.endChunk());
+                    endSent = true;
+                }
+            }
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    // used for chunk parsing/end
+    ChunkState chunk = new ChunkState();
+    ChunkState rchunk = new ChunkState();
+    static final int NEED_MORE = -1;
+    static final int ERROR = -4;
+    static final int DONE = -5;
+
+
+    static class ChunkState {
+        static byte[] END_CHUNK_BYTES = {
+            (byte) '\r', (byte) '\n',
+            (byte) '0',
+            (byte) '\r', (byte) '\n',
+            (byte) '\r', (byte) '\n'};
+
+
+        int partialChunkLen;
+        boolean readDigit = false;
+        boolean trailer = false;
+        protected boolean needChunkCrlf = false;
+
+        // Buffer used for chunk length conversion.
+        protected byte[] sendChunkLength = new byte[10];
+
+        /** End chunk marker - will include chunked end or empty */
+        protected BBuffer endSendBuffer = BBuffer.wrapper();
+
+        public ChunkState() {
+            sendChunkLength[8] = (byte) '\r';
+            sendChunkLength[9] = (byte) '\n';
+        }
+
+        void recycle() {
+            partialChunkLen = 0;
+            readDigit = false;
+            trailer = false;
+            needChunkCrlf = false;
+            endSendBuffer.recycle();
+        }
+
+        /**
+         * Parse the header of a chunk.
+         * A chunk header can look like
+         * A10CRLF
+         * F23;chunk-extension to be ignoredCRLF
+         * The letters before CRLF but after the trailer mark, must be valid hex digits,
+         * we should not parse F23IAMGONNAMESSTHISUP34CRLF as a valid header
+         * according to spec
+         */
+        int parseChunkHeader(IOBuffer buffer) throws IOException {
+            if (buffer.peekFirst() == null) {
+                return NEED_MORE;
+            }
+            if (needChunkCrlf) {
+                // TODO: Trailing headers
+                int c = buffer.read();
+                if (c == BBuffer.CR) {
+                    if (buffer.peekFirst() == null) {
+                        return NEED_MORE;
+                    }
+                    c = buffer.read();
+                }
+                if (c == BBuffer.LF) {
+                    needChunkCrlf = false;
+                } else {
+                    System.err.println("Bad CRLF " + c);
+                    return ERROR;
+                }
+            }
+
+            while (true) {
+                if (buffer.peekFirst() == null) {
+                    return NEED_MORE;
+                }
+                int c = buffer.read();
+
+                if (c == BBuffer.CR) {
+                    continue;
+                } else if (c == BBuffer.LF) {
+                    break;
+                } else if (c == HttpChannel.SEMI_COLON) {
+                    trailer = true;
+                } else if (c == BBuffer.SP) {
+                    // ignore
+                } else if (trailer) {
+                    // ignore
+                } else {
+                    //don't read data after the trailer
+                    if (Hex.DEC[c] != -1) {
+                        readDigit = true;
+                        partialChunkLen *= 16;
+                        partialChunkLen += Hex.DEC[c];
+                    } else {
+                        //we shouldn't allow invalid, non hex characters
+                        //in the chunked header
+                        log.info("Chunk parsing error1 " + c + " " + buffer);
+                        //http.abort("Chunk error");
+                        return ERROR;
+                    }
+                }
+            }
+
+            if (!readDigit) {
+                log.info("Chunk parsing error2 " + buffer);
+                return ERROR;
+            }
+
+            needChunkCrlf = true;  // next time I need to parse CRLF
+            int result = partialChunkLen;
+            partialChunkLen = 0;
+            trailer = false;
+            readDigit = false;
+            return result;
+        }
+
+
+        ByteBuffer prepareChunkHeader(int current) {
+            int pos = 7; // 8, 9 are CRLF
+            while (current > 0) {
+                int digit = current % 16;
+                current = current / 16;
+                sendChunkLength[pos--] = Hex.HEX[digit];
+            }
+            if (needChunkCrlf) {
+                sendChunkLength[pos--] = (byte) '\n';
+                sendChunkLength[pos--] = (byte) '\r';
+            } else {
+                needChunkCrlf = true;
+            }
+            // TODO: pool - this may stay in the queue while we flush more
+            ByteBuffer chunkBB = ByteBuffer.allocate(16);
+            chunkBB.put(sendChunkLength, pos + 1, 9 - pos);
+            chunkBB.flip();
+            return chunkBB;
+        }
+
+        public BBuffer endChunk() {
+            if (! needChunkCrlf) {
+                endSendBuffer.setBytes(END_CHUNK_BYTES, 2,
+                        END_CHUNK_BYTES.length - 2); // CRLF
+            } else { // 0
+                endSendBuffer.setBytes(END_CHUNK_BYTES, 0,
+                        END_CHUNK_BYTES.length);
+            }
+            return endSendBuffer;
+        }
+    }
+
+    static class BodyState {
+        /** response: HEAD or  1xx, 204, 304 status
+         *  req: missing content-length or transfer-encoding
+         */
+        protected boolean noBody = false;
+        protected boolean chunked = false;
+        protected long contentLength = -1; // C-L header
+        /** Bytes remaining in the current chunk or body ( if CL ) */
+        protected long remaining = 0; // both chunked and C-L
+
+        public void recycle() {
+            chunked = false;
+            remaining = 0;
+            contentLength = -1;
+            noBody = false;
+        }
+        public boolean isContentDelimited() {
+            return chunked || contentLength >= 0;
+        }
+
+    }
+
+    public String toString() {
+        if (switchedProtocol != null) {
+            return switchedProtocol.toString();
+        }
+
+        return (serverMode ? "SR " : "CL ") +
+        (keepAlive() ? " KA " : "") +
+        (headersReceived ? " HEAD " : "") +
+        (bodyReceived ? " BODY " : "")
+        ;
+    }
+
+    @Override
+    public void handleConnected(IOChannel net) throws IOException {
+        HttpChannel httpCh = activeHttp;
+
+        if (!net.isOpen()) {
+            httpCh.abort(net.lastException());
+            return;
+        }
+
+        boolean ssl = httpCh.getRequest().isSecure();
+        if (ssl) {
+            String[] hostPort = httpCh.getTarget().split(":");
+
+            IOChannel ch1 = httpConnector.sslProvider.channel(net,
+                    hostPort[0], Integer.parseInt(hostPort[1]));
+            //net.setHead(ch1);
+            net = ch1;
+        }
+        if (httpConnector.debugHttp) {
+            net = DumpChannel.wrap("Http-Client-", net);
+        }
+
+        setSink(net);
+
+        sendRequest(httpCh);
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java
new file mode 100644
index 0000000..cb4b2ad
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpChannel.java
@@ -0,0 +1,830 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+import java.io.IOException;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.FutureCallbacks;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+/**
+ * HTTP async client and server, based on tomcat NIO/APR connectors
+ *
+ * 'Input', 'read', 'Recv' refers to information we get from the remote side -
+ * the request body for server-mode or response body for client.
+ *
+ * 'Output', 'write', 'Send' is for info we send - the post in client mode
+ * and the response body for server mode.
+ *
+ * @author Costin Manolache
+ */
+public class HttpChannel extends IOChannel {
+
+    static final int HEADER_SIZE = 8192;
+
+    static AtomicInteger serCnt = new AtomicInteger();
+
+    public static final String CONTENT_LENGTH= "Content-Length";
+
+    public static final String HTTP_10 = "HTTP/1.0";
+
+    public static final String HTTP_11 = "HTTP/1.1";
+
+    /**
+     * SEMI_COLON.
+     */
+    public static final byte SEMI_COLON = (byte) ';';
+
+    public static final byte QUESTION = (byte) '?';
+
+
+    protected static Logger log = Logger.getLogger("HttpChannel");
+
+
+    boolean debug = false;
+
+    // ---- Callbacks and locks
+
+    FutureCallbacks<HttpChannel> doneLock = new FutureCallbacks<HttpChannel>();
+    FutureCallbacks<HttpChannel> headersReceivedLock =
+            new FutureCallbacks<HttpChannel>();
+    /**
+     * Called when the incoming headers have been received.
+     * ( response for client mode, request for server mode )
+     * @throws IOException
+     */
+    protected HttpService httpService;
+    /**
+     * Called when:
+     *  - body sent
+     *  - body received
+     *  - release() called - either service() done or client done with the
+     *  buffers.
+     *
+     *  After this callback:
+     *  - socket closed if closeOnEndSend, or put in keep-alive
+     *  - AsyncHttp.recycle()
+     *  - returned to the pool.
+     */
+    private RequestCompleted doneAllCallback;
+    protected boolean sendReceiveDone = false;
+
+    // Will be signalled (open) when the buffer is empty.
+    FutureCallbacks<IOChannel> flushLock = new FutureCallbacks<IOChannel>();
+
+    FutureCallbacks<HttpChannel> doneFuture;
+    boolean doneCallbackCalled = false;
+
+
+    // ----------
+
+    // Set if Exect: 100-continue was set on reqest.
+    // If this is the case - body won't be sent until
+    // server responds ( client ) and server will only
+    // read body after ack() - or skip to next request
+    // without swallowing the body.
+    protected boolean expectation = false;
+
+    /** Ready for recycle, if send/receive are done */
+    protected boolean release = false;
+
+    // -----------
+
+    protected boolean headersDone = false;
+    protected boolean error = false;
+    protected boolean abortDone = false;
+
+
+    protected int ser; // id - for jmx registration and logs
+    protected int channelId;
+
+    /**
+     * Null after endSendReceive and before sending the request
+     */
+    HttpConnection conn;
+
+    HttpConnector httpConnector;
+
+    // Different ways to point to request response (server/client)
+    HttpRequest httpReq;
+    HttpResponse httpRes;
+    HttpMessage inMessage;
+    HttpMessage outMessage;
+    // receive can be for request ( server mode ) or response ( client )
+    IOBuffer receiveBody = new IOBuffer();
+
+    // notify us that user called close()
+    IOBuffer sendBody = new IOBuffer() {
+        public void close() throws IOException {
+            if (isAppendClosed()) {
+                return;
+            }
+            super.close();
+            outClosed();
+        }
+    };
+
+
+    // Server side only
+    protected String serverHeader = "TomcatLite";
+
+    long ioTimeout = 30 * 60000; // 30 min seems high enough
+
+
+    public HttpChannel() {
+        ser = serCnt.incrementAndGet();
+        httpReq = new HttpRequest(this);
+        httpRes = new HttpResponse(this);
+        init();
+        serverMode(false);
+    }
+
+    /**
+     * Close the connection, return to pool. Called if a
+     * framing error happens, or if we want to force the connection
+     * to close, without waiting for all data to be sent/received.
+     * @param t
+     *
+     * @throws IOException
+     */
+    public void abort(Throwable t) {
+        abort(t.toString());
+    }
+
+    public void abort(String t)  {
+        synchronized (this) {
+            if (abortDone) {
+                return;
+            }
+            abortDone = true;
+        }
+        try {
+            checkRelease();
+            trace("abort " + t);
+            if (conn != null) {
+                conn.abort(this, t);
+            }
+            inMessage.state = HttpMessage.State.DONE;
+            outMessage.state = HttpMessage.State.DONE;
+            sendReceiveDone = true;
+            error = true;
+            handleEndSendReceive();
+        } catch (Throwable ex) {
+            log.severe("Exception in abort " + ex);
+        }
+    }
+
+    /**
+     * If release was called - throw exception, you shouldn't use
+     * the object again.
+     * @throws IOException
+     */
+    private void checkRelease() throws IOException {
+        if (release && sendReceiveDone) {
+            throw new IOException("Object released");
+        }
+    }
+
+    public IOChannel getSink() {
+        if (conn == null) {
+            return null;
+        }
+        return conn.getSink();
+    }
+
+
+    /**
+     * Called when the request is done. Need to send remaining byte.
+     *
+     */
+    public void complete() throws IOException {
+        checkRelease();
+        if (!getOut().isAppendClosed()) {
+            getOut().close();
+        }
+        if (!getIn().isAppendClosed()) {
+            getIn().close();
+        }
+
+        startSending();
+   }
+
+    public int doRead(BBuffer chunk)
+            throws IOException {
+        checkRelease();
+        BBucket next = null;
+        while (true) {
+            getIn().waitData(0);
+            next = (BBucket) getIn().popFirst();
+            if (next != null) {
+                break;
+            } else if (getIn().isAppendClosed()) {
+                return -1;
+            } else {
+                System.err.println("Spurious waitData signal, no data");
+            }
+        }
+        chunk.append(next.array(), next.position(), next.remaining());
+        int read =  next.remaining();
+        next.release();
+        return read;
+    }
+
+    public HttpConnector getConnector() {
+        return httpConnector;
+    }
+
+    public boolean getError() {
+        return error;
+    }
+
+    // ---------------- Writting -------------------------------
+
+    public String getId() {
+        return Integer.toString(ser);
+    }
+
+    public IOBuffer getIn() {
+        return receiveBody;
+    }
+
+
+    public long getIOTimeout() {
+        return ioTimeout;
+    }
+
+    // TODO: replace with getSocketChannel - used for remote addr, etc
+    public IOChannel getNet() {
+        if (conn == null) {
+            return null;
+        }
+        return conn.getSink();
+    }
+
+
+    public IOBuffer getOut() {
+        return sendBody;
+    }
+
+    public HttpRequest getRequest() {
+        return httpReq;
+    }
+
+
+    public HttpResponse getResponse() {
+        return httpRes;
+    }
+
+
+    public String getState() {
+        return
+            conn +
+            "RCV=[" + inMessage.state.toString() + " " +
+            receiveBody.toString()
+            + "] SND=[" + outMessage.state.toString()
+            + " " + sendBody.toString() + "]";
+    }
+
+
+    public String getStatus() {
+        return getResponse().getStatus() + " " + getResponse().getMessage();
+    }
+
+
+    public String getTarget() {
+        if (target == null) {
+            return ":0"; // server mode ?
+        }
+        return target.toString();
+    }
+
+
+    /**
+     * Called from IO thread, after the request body
+     * is completed ( or if there is no req body )
+     * @throws IOException
+     */
+    protected void handleEndReceive() throws IOException {
+        if (inMessage.state == HttpMessage.State.DONE) {
+            return;
+        }
+        if (debug) {
+            trace("END_RECV");
+        }
+        getIn().close();
+
+        inMessage.state = HttpMessage.State.DONE;
+        handleEndSendReceive();
+    }
+
+    /*
+     * Called when sending, receiving and processing is done.
+     * Can be called:
+     *  - from IO thread, if this is a result of a read/write event that
+     *  finished the send/recev pair.
+     *  - from an arbitrary thread, if read was complete and the last write
+     *  was a success and done in that thread ( write is not bound to IO thr)
+     *
+     */
+    protected void handleEndSendReceive() throws IOException {
+        // make sure the callback was called ( needed for abort )
+        handleHeadersReceived(inMessage);
+
+        this.doneLock.signal(this);
+        synchronized (this) {
+            if (doneCallbackCalled) {
+                return;
+            }
+            if (outMessage.state != HttpMessage.State.DONE ||
+                    inMessage.state != HttpMessage.State.DONE) {
+                return;
+            }
+            doneCallbackCalled = true;
+        }
+
+        getIn().close();
+
+        if (doneAllCallback != null) {
+            doneAllCallback.handle(this, error ? new Throwable() : null);
+        }
+
+        if (conn != null) {
+            conn.endSendReceive(this);
+        }
+
+        conn = null;
+
+        if (debug) {
+            trace("END_SEND_RECEIVE"
+                    + (release ? " REL" : ""));
+        }
+
+        synchronized(this) {
+            sendReceiveDone = true;
+            maybeRelease();
+        }
+    }
+
+    /**
+     * called from IO thread OR servlet thread when last block has been sent.
+     * If not using the socket ( net.getOut().flushCallback ) - this must
+     * be called explicitely after flushing the body.
+     */
+    void handleEndSent() throws IOException {
+        if (outMessage.state == HttpMessage.State.DONE) {
+            // Only once.
+            if (debug) {
+                trace("Duplicate END SEND");
+            }
+            return;
+        }
+        outMessage.state = HttpMessage.State.DONE;
+
+        getOut().close();
+
+        // Make sure the send/receive callback is called once
+        if (debug) {
+            trace("END_SEND");
+        }
+        handleEndSendReceive();
+    }
+
+    // ----- End Selector thread callbacks ----
+    public void handleError(String type) {
+        System.err.println("Error " + type + " " + outMessage.state);
+    }
+
+    void handleHeadersReceived(HttpMessage in) throws IOException {
+        if (!headersDone) {
+            headersDone = true;
+            headersReceivedLock.signal(this);
+            if (httpService != null) {
+                try {
+                    httpService.service(getRequest(), getResponse());
+                } catch (Throwable t) {
+                    t.printStackTrace();
+                    abort(t);
+                }
+            }
+        }
+    }
+
+
+    private void init() {
+        headersDone = false;
+        sendReceiveDone = false;
+
+        receiveBody.recycle();
+        sendBody.recycle();
+        expectation = false;
+
+        error = false;
+        abortDone = false;
+
+
+        getRequest().recycle();
+        getResponse().recycle();
+        target = null;
+
+        doneLock.recycle();
+        headersReceivedLock.recycle();
+        flushLock.recycle();
+
+        doneCallbackCalled = false;
+        // Will be set again after pool
+        setHttpService(null);
+        doneAllCallback = null;
+        release = false;
+    }
+
+    public boolean isDone() {
+        return outMessage.state == HttpMessage.State.DONE && inMessage.state == HttpMessage.State.DONE;
+    }
+
+    /**
+     * Called when all done:
+     *  - service finished ( endService was called )
+     *  - output written
+     *  - input read
+     *
+     * or by abort().
+     *
+     * @throws IOException
+     */
+    private void maybeRelease() throws IOException {
+        synchronized (this) {
+            if (release && sendReceiveDone) {
+                if (debug) {
+                    trace("RELEASE");
+                }
+                if (getConnector() != null) {
+                    getConnector().returnToPool(this);
+                } else {
+                    log.severe("Attempt to release with no pool");
+                }
+            }
+        }
+    }
+
+
+    /*
+    The field-content does not include any leading or trailing LWS:
+    linear white space occurring before the first non-whitespace
+    character of the field-value or after the last non-whitespace
+     character of the field-value. Such leading or trailing LWS MAY
+     be removed without changing the semantics of the field value.
+     Any LWS that occurs between field-content MAY be replaced with
+     a single Http11Parser.SP before interpreting the field value or forwarding
+     the message downstream.
+     */
+    int normalizeHeader(BBuffer value) {
+        byte[] buf = value.array();
+        int cstart = value.position();
+        int end = value.limit();
+
+        int realPos = cstart;
+        int lastChar = cstart;
+        byte chr = 0;
+        boolean gotSpace = true;
+
+        for (int i = cstart; i < end; i++) {
+            chr = buf[i];
+            if (chr == BBuffer.CR) {
+                // skip
+            } else if(chr == BBuffer.LF) {
+                // skip
+            } else if (chr == BBuffer.SP || chr == BBuffer.HT) {
+                if (gotSpace) {
+                    // skip
+                } else {
+                    buf[realPos++] = BBuffer.SP;
+                    gotSpace = true;
+                }
+            } else {
+                buf[realPos++] = chr;
+                lastChar = realPos; // to skip trailing spaces
+                gotSpace = false;
+            }
+        }
+        realPos = lastChar;
+
+        // so buffer is clean
+        for (int i = realPos; i < end; i++) {
+            buf[i] = BBuffer.SP;
+        }
+        value.setEnd(realPos);
+        return realPos;
+    }
+
+
+    protected void recycle() {
+        if (debug) {
+            trace("RECYCLE");
+        }
+        init();
+    }
+
+    /**
+     * Finalize sending and receiving.
+     * Indicates client is no longer interested, some IO may still be in flight.
+     * If in a POST and you're not interested in the body - it may be
+     * better to call abort().
+     *
+     * MUST be called to allow connection reuse and pooling.
+     *
+     * @throws IOException
+     */
+    public void release() throws IOException {
+        synchronized(this) {
+            if (release) {
+                return;
+            }
+            trace("RELEASE");
+            release = true;
+            // If send/receive is done - we can reuse this object
+            maybeRelease();
+        }
+    }
+
+    public void send() throws IOException {
+        checkRelease();
+        if (httpReq == inMessage) {
+            conn.sendResponseHeaders(this);
+        } else {
+            if (getRequest().isCommitted()) {
+                return;
+            }
+            getRequest().setCommitted(true);
+
+            outMessage.state = HttpMessage.State.HEAD;
+
+            getConnector().connectAndSend(this);
+        }
+    }
+
+    /** Called when the outgoing stream is closed:
+     * - by an explicit call to close()
+     * - when all content has been sent.
+     */
+    protected void outClosed() throws IOException {
+        if (conn != null) {
+            conn.outClosed(this);
+        }
+    }
+
+    public HttpChannel serverMode(boolean enabled) {
+        if (enabled) {
+            httpReq.setBody(receiveBody);
+            httpRes.setBody(sendBody);
+            inMessage = httpReq;
+            outMessage = httpRes;
+        } else {
+            httpReq.setBody(sendBody);
+            httpRes.setBody(receiveBody);
+            inMessage = httpRes;
+            outMessage = httpReq;
+        }
+        if (debug) {
+        }
+        return this;
+    }
+
+    public void setCompletedCallback(RequestCompleted doneAllCallback)
+            throws IOException {
+        this.doneAllCallback = doneAllCallback;
+        synchronized (this) {
+            if (doneCallbackCalled) {
+                return;
+            }
+            if (outMessage.state != HttpMessage.State.DONE || inMessage.state != HttpMessage.State.DONE) {
+                return;
+            }
+        }
+        doneCallbackCalled = true;
+        if (doneAllCallback != null) {
+            doneAllCallback.handle(this, error ? new Throwable() : null);
+        }
+    }
+
+    public void setConnector(HttpConnector pool) {
+        this.httpConnector = pool;
+    }
+
+    public void setHttpService(HttpService headersReceivedCallback) {
+        this.httpService = headersReceivedCallback;
+    }
+
+    public void setIOTimeout(long timeout) {
+        ioTimeout = timeout;
+    }
+
+
+    public void setTarget(String host) {
+        this.target = host;
+    }
+
+    public void startSending() throws IOException {
+        checkRelease();
+        if (conn != null) {
+            conn.startSending(this);
+        }
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("id=").append(ser)
+            .append(",rs=").append(getState())
+            .append(")");
+        return sb.toString();
+    }
+
+
+    void trace(String msg) {
+        if(debug) {
+            log.info(this.toString() + " " + msg + " done=" + doneCallbackCalled);
+        }
+    }
+
+    @Override
+    public void waitFlush(long timeMs) throws IOException {
+        if (getOut().getBufferCount() == 0) {
+            return;
+        }
+        flushLock.waitSignal(timeMs);
+    }
+
+    public HttpChannel setConnection(HttpConnection conn) {
+        this.conn = conn;
+        return this;
+    }
+
+    /**
+     * Normalize URI.
+     * <p>
+     * This method normalizes "\", "//", "/./" and "/../". This method will
+     * return false when trying to go above the root, or if the URI contains
+     * a null byte.
+     *
+     * @param uriMB URI to be normalized, will be modified
+     */
+    public static boolean normalize(BBuffer uriBC) {
+
+        byte[] b = uriBC.array();
+        int start = uriBC.getStart();
+        int end = uriBC.getEnd();
+
+        // URL * is acceptable
+        if ((end - start == 1) && b[start] == (byte) '*')
+            return true;
+
+        if (b[start] != '/') {
+            // TODO: http://.... URLs
+            return true;
+        }
+
+        int pos = 0;
+        int index = 0;
+
+        // Replace '\' with '/'
+        // Check for null byte
+        for (pos = start; pos < end; pos++) {
+            if (b[pos] == (byte) '\\')
+                b[pos] = (byte) '/';
+            if (b[pos] == (byte) 0)
+                return false;
+        }
+
+        // The URL must start with '/'
+        if (b[start] != (byte) '/') {
+            return false;
+        }
+
+        // Replace "//" with "/"
+        for (pos = start; pos < (end - 1); pos++) {
+            if (b[pos] == (byte) '/') {
+                while ((pos + 1 < end) && (b[pos + 1] == (byte) '/')) {
+                    copyBytes(b, pos, pos + 1, end - pos - 1);
+                    end--;
+                }
+            }
+        }
+
+        // If the URI ends with "/." or "/..", then we append an extra "/"
+        // Note: It is possible to extend the URI by 1 without any side effect
+        // as the next character is a non-significant WS.
+        if (((end - start) >= 2) && (b[end - 1] == (byte) '.')) {
+            if ((b[end - 2] == (byte) '/')
+                    || ((b[end - 2] == (byte) '.')
+                            && (b[end - 3] == (byte) '/'))) {
+                b[end] = (byte) '/';
+                end++;
+            }
+        }
+
+        uriBC.setEnd(end);
+
+        index = 0;
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            index = uriBC.indexOf("/./", 0, 3, index);
+            if (index < 0)
+                break;
+            copyBytes(b, start + index, start + index + 2,
+                    end - start - index - 2);
+            end = end - 2;
+            uriBC.setEnd(end);
+        }
+
+        index = 0;
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            index = uriBC.indexOf("/../", 0, 4, index);
+            if (index < 0)
+                break;
+            // Prevent from going outside our context
+            if (index == 0)
+                return false;
+            int index2 = -1;
+            for (pos = start + index - 1; (pos >= 0) && (index2 < 0); pos --) {
+                if (b[pos] == (byte) '/') {
+                    index2 = pos;
+                }
+            }
+            copyBytes(b, start + index2, start + index + 3,
+                    end - start - index - 3);
+            end = end + index2 - index - 3;
+            uriBC.setEnd(end);
+            index = index2;
+        }
+
+        //uriBC.setBytes(b, start, end);
+        uriBC.setEnd(end);
+        return true;
+
+    }
+
+    /**
+     * Copy an array of bytes to a different position. Used during
+     * normalization.
+     */
+    private static void copyBytes(byte[] b, int dest, int src, int len) {
+        for (int pos = 0; pos < len; pos++) {
+            b[pos + dest] = b[pos + src];
+        }
+    }
+
+
+    /**
+     * This method will be called when the http headers have been received -
+     * the body may or may not be available.
+     *
+     * In server mode this is equivalent with a servlet request.
+     * This is also called for http client, when the response headers
+     * are received.
+     *
+     * TODO: rename it to HttMessageReceived or something similar.
+     */
+    public static interface HttpService {
+        void service(HttpRequest httpReq, HttpResponse httpRes) throws IOException;
+    }
+
+    /**
+     * Called when both request and response bodies have been sent/
+     * received. After this call the HttpChannel will be disconnected
+     * from the http connection, which can be used for other requests.
+     */
+    public static interface RequestCompleted {
+        void handle(HttpChannel data, Object extraData) throws IOException;
+    }
+
+    Runnable dispatcherRunnable = new Runnable() {
+        @Override
+        public void run() {
+            getConnector().getDispatcher().runService(HttpChannel.this);
+        }
+    };
+
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpClient.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpClient.java
new file mode 100644
index 0000000..d537962
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpClient.java
@@ -0,0 +1,22 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import org.apache.tomcat.lite.io.SocketConnector;
+import org.apache.tomcat.lite.io.SslProvider;
+import org.apache.tomcat.lite.io.jsse.JsseSslProvider;
+
+/**
+ * Entry point for http client code.
+ *
+ * ( initial version after removing 'integration', will add settings,
+ * defaults, helpers )
+ */
+public class HttpClient {
+    static SslProvider sslConC = new JsseSslProvider();
+
+    public synchronized static HttpConnector newClient() {
+        return new HttpConnector(new SocketConnector()).withSsl(sslConC);
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java
new file mode 100644
index 0000000..ab56830
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnectionPool.java
@@ -0,0 +1,399 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.Queue;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+/**
+ * - Holds references to all active and kept-alive connections.
+ * - makes decisions on accepting more connections, closing old
+ * connections, etc
+ *
+ */
+public class HttpConnectionPool {
+    // TODO: add timeouts, limits per host/total, expire old entries
+
+    public static interface HttpConnectionPoolEvents {
+        public void newTarget(RemoteServer host);
+
+        public void targetRemoved(RemoteServer host);
+
+        public void newConnection(RemoteServer host, HttpConnection con);
+        public void closedConnection(RemoteServer host, HttpConnection con);
+    }
+
+    /**
+     * Connections for one remote host.
+     * This should't be restricted by IP:port or even hostname,
+     * for example if a server has multiple IPs or LB replicas - any would work.
+     */
+    public static class RemoteServer {
+        // all access sync on RemoteServer
+        private SpdyConnection spdy;
+
+        // all access sync on RemoteServer
+        private ArrayList<Http11Connection> connections
+            = new ArrayList<Http11Connection>();
+
+        Queue<HttpChannel> pending = new LinkedList<HttpChannel>();
+
+
+        // TODO: setter, default from connector
+        private int maxConnections = 20;
+
+        AtomicInteger activeRequests = new AtomicInteger();
+        AtomicInteger totalRequests = new AtomicInteger();
+        private volatile long lastActivity;
+
+        public String target;
+
+        public synchronized List<HttpConnector.HttpConnection> getConnections()
+        {
+            return new ArrayList<HttpConnection>(connections);
+        }
+
+        public synchronized Collection<HttpChannel> getActives() {
+            ArrayList<HttpChannel> actives = new ArrayList();
+            for (Http11Connection con: connections) {
+                if (con.activeHttp != null) {
+                    actives.add(con.activeHttp);
+                }
+            }
+            if (spdy != null) {
+                actives.addAll(spdy.getActives());
+            }
+
+            return actives;
+        }
+
+        public synchronized void touch() {
+            lastActivity = System.currentTimeMillis();
+        }
+    }
+
+    private HttpConnectionPoolEvents poolEvents;
+
+    private static Logger log = Logger.getLogger("HttpConnector");
+
+    // visible for debugging - will be made private, with accessor
+    /**
+     * Map from client names to socket pools.
+     */
+    public Map<CharSequence, HttpConnectionPool.RemoteServer> hosts = new HashMap<CharSequence,
+        HttpConnectionPool.RemoteServer>();
+
+    // Statistics
+    public AtomicInteger waitingSockets = new AtomicInteger();
+    public AtomicInteger closedSockets = new AtomicInteger();
+
+    public AtomicInteger hits = new AtomicInteger();
+    public AtomicInteger misses = new AtomicInteger();
+    public AtomicInteger queued = new AtomicInteger();
+
+    public AtomicInteger activeRequests = new AtomicInteger();
+
+    private static boolean debug = false;
+    HttpConnector httpConnector;
+
+    public HttpConnectionPool(HttpConnector httpConnector) {
+        this.httpConnector = httpConnector;
+    }
+
+    public int getTargetCount() {
+        return hosts.size();
+    }
+
+    public int getSocketCount() {
+        return waitingSockets.get();
+    }
+
+    public int getClosedSockets() {
+        return closedSockets.get();
+    }
+
+    public Set<CharSequence> getKeepAliveTargets() {
+        return hosts.keySet();
+    }
+
+    public List<RemoteServer> getServers() {
+        return new ArrayList<RemoteServer>(hosts.values());
+    }
+
+    public void setEvents(HttpConnectionPoolEvents events) {
+        this.poolEvents = events;
+    }
+    /**
+     * Stop all cached connections.
+     */
+    public void clear() throws IOException {
+        synchronized (hosts) {
+            int active = 0;
+            for (RemoteServer rs: hosts.values()) {
+                synchronized (rs) {
+                    int hostActive = 0;
+                    if (rs.spdy != null) {
+                        if (rs.spdy.channels.size() == 0) {
+                            rs.spdy.close();
+                            rs.spdy = null;
+                        } else {
+                            hostActive += rs.spdy.channels.size();
+                        }
+                    }
+                    for (Http11Connection con: rs.connections) {
+                        if (con.activeHttp == null) {
+                            con.close();
+                        } else {
+                            hostActive++;
+                        }
+                    }
+                    if (hostActive != rs.activeRequests.get()) {
+                        log.warning("Active missmatch " + rs.target + " " +
+                                hostActive + " "
+                                + rs.activeRequests.get());
+                        rs.activeRequests.set(hostActive);
+                    }
+                    active += hostActive;
+                }
+            }
+            if (active != this.activeRequests.get()) {
+                log.warning("Active missmatch " + active + " "
+                        + activeRequests.get());
+                activeRequests.set(active);
+            }
+        }
+    }
+
+    /**
+     * Stop all active and cached connections
+     * @throws IOException
+     */
+    public void abort() throws IOException {
+        // TODO
+        clear();
+        hosts.clear();
+    }
+
+    /**
+     * @param key host:port, or some other key if multiple hosts:ips
+     * are connected to equivalent servers ( LB )
+     * @param httpCh
+     * @throws IOException
+     */
+    public void send(HttpChannel httpCh)
+            throws IOException {
+        String target = httpCh.getTarget();
+        HttpConnection con = null;
+        // TODO: check ssl on connection - now if a second request
+        // is received on a ssl connection - we just send it
+        boolean ssl = httpCh.getRequest().isSecure();
+
+        HttpConnectionPool.RemoteServer remoteServer = null;
+        synchronized (hosts) {
+            remoteServer = hosts.get(target);
+            if (remoteServer == null) {
+                remoteServer = new HttpConnectionPool.RemoteServer();
+                remoteServer.target = target;
+                hosts.put(target, remoteServer);
+            }
+        }
+
+        // TODO: remove old servers and connections
+
+        // Temp magic - until a better negotiation is defined
+        boolean forceSpdy = "SPDY/1.0".equals(httpCh.getRequest().getProtocol());
+        if (forceSpdy) {
+            // switch back the protocol
+            httpCh.getRequest().setProtocol("HTTP/1.1");
+        }
+
+        activeRequests.incrementAndGet();
+        remoteServer.activeRequests.incrementAndGet();
+
+        // if we already have a spdy connection or explicitely
+        // requested.
+        if (forceSpdy || remoteServer.spdy != null) {
+            synchronized (remoteServer) {
+                if (remoteServer.spdy == null) {
+                    remoteServer.spdy = new SpdyConnection(httpConnector,
+                            remoteServer);
+                }
+                con = remoteServer.spdy;
+            }
+
+            // Will be queued - multiple threads may try to send
+            // at the same time, and we need to queue anyways.
+            con.sendRequest(httpCh);
+        } else {
+            synchronized (remoteServer) {
+                Http11Connection hcon;
+                for (int i = 0; i < remoteServer.connections.size(); i++) {
+                    hcon = (Http11Connection) remoteServer.connections.get(i);
+                    if (hcon != null && hcon.activeHttp == null) {
+                        hcon.beforeRequest(); // recycle
+
+                        hcon.activeHttp = httpCh;
+                        con = hcon;
+                        break;
+                    }
+                }
+                if (con == null) {
+//                    if (remoteServer.connections.size() > remoteServer.maxConnections) {
+//                        remoteServer.pending.add(httpCh);
+//                        queued.incrementAndGet();
+//                        if (debug) {
+//                            log.info("Queue: " + target + " " + remoteServer.connections.size());
+//                        }
+//                        return;
+//                    }
+                    hcon = new Http11Connection(httpConnector);
+                    hcon.setTarget(target);
+                    hcon.activeHttp = httpCh;
+                    hcon.remoteHost = remoteServer;
+                    remoteServer.connections.add(hcon);
+                    con = hcon;
+                }
+            }
+
+
+            // we got a connection - make sure we're connected
+            http11ConnectOrSend(httpCh, target, con, ssl);
+        }
+    }
+
+    private void http11ConnectOrSend(HttpChannel httpCh, String target,
+            HttpConnection con, boolean ssl) throws IOException {
+        httpCh.setConnection(con);
+
+        if (con.isOpen()) {
+            hits.incrementAndGet();
+//            if (debug) {
+//                log.info("HTTP_CONNECT: Reuse connection " + target + " " + this);
+//            }
+            con.sendRequest(httpCh);
+        } else {
+            misses.incrementAndGet();
+            if (debug) {
+                log.info("HTTP_CONNECT: Start connection " + target + " " + this);
+            }
+            httpConnect(httpCh, target, ssl,
+                    (Http11Connection) con);
+        }
+    }
+
+    void httpConnect(HttpChannel httpCh, String target,
+            boolean ssl, IOConnector.ConnectedCallback cb)
+            throws IOException {
+        if (debug) {
+            log.info("HTTP_CONNECT: New connection " + target);
+        }
+        String[] hostPort = target.split(":");
+
+        int targetPort = ssl ? 443 : 80;
+        if (hostPort.length > 1) {
+            targetPort = Integer.parseInt(hostPort[1]);
+        }
+
+        httpConnector.getIOConnector().connect(hostPort[0], targetPort,
+                cb);
+    }
+
+    public void afterRequest(HttpChannel http, HttpConnection con,
+            boolean keepAlive)
+                throws IOException {
+        activeRequests.decrementAndGet();
+        if (con.remoteHost != null) {
+            con.remoteHost.touch();
+            con.remoteHost.activeRequests.decrementAndGet();
+        }
+        if (con.serverMode) {
+            afterServerRequest(con, keepAlive);
+        } else {
+            afterClientRequest(con);
+        }
+    }
+
+    private void afterClientRequest(HttpConnection con)
+            throws IOException {
+        RemoteServer remoteServer = con.remoteHost;
+        HttpChannel req = null;
+
+        // If we have pending requests ( because too many active limit ), pick
+        // one and send it.
+        synchronized (remoteServer) {
+            // If closed - we can remove the object - or
+            // let a background thread do it, in case it's needed
+            // again.
+            if (remoteServer.pending.size() == 0) {
+                con.activeHttp = null;
+                return;
+            }
+            req = remoteServer.pending.remove();
+            con.activeHttp = req;
+            if (debug) {
+                log.info("After request: send pending " + remoteServer.pending.size());
+            }
+        }
+
+        http11ConnectOrSend(req, con.getTarget().toString(),
+                con, req.getRequest().isSecure());
+    }
+
+    RemoteServer serverPool = new RemoteServer();
+
+    public void afterServerRequest(HttpConnection con, boolean keepAlive)
+            throws IOException {
+        con.activeHttp = null;
+        if (!keepAlive) {
+            synchronized (serverPool) {
+                // I could also reuse the object.
+                serverPool.connections.remove(con);
+            }
+        }
+    }
+
+    public HttpConnection accepted(IOChannel accepted) {
+        Http11Connection con = new Http11Connection(httpConnector);
+        con.remoteHost = serverPool;
+        synchronized (serverPool) {
+            serverPool.connections.add(con);
+        }
+        return con;
+    }
+
+
+    // Called by handleClosed
+    void stopKeepAlive(IOChannel schannel) {
+        CharSequence target = schannel.getTarget();
+        HttpConnectionPool.RemoteServer remoteServer = null;
+        synchronized (hosts) {
+            remoteServer = hosts.get(target);
+            if (remoteServer == null) {
+                return;
+            }
+        }
+        synchronized (remoteServer) {
+            if (remoteServer.connections.remove(schannel)) {
+                waitingSockets.decrementAndGet();
+                if (remoteServer.connections.size() == 0) {
+                    hosts.remove(target);
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java
new file mode 100644
index 0000000..0b4708d
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpConnector.java
@@ -0,0 +1,514 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Timer;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.DumpChannel;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.SslProvider;
+import org.apache.tomcat.lite.io.IOConnector.DataReceivedCallback;
+
+/**
+ * Manages HttpChannels and associated socket pool.
+ *
+ *
+ * @author Costin Manolache
+ */
+public class HttpConnector {
+
+    public static interface HttpChannelEvents {
+        /** HttpChannel object created. It'll be used many times.
+         * @throws IOException
+         */
+        public void onCreate(HttpChannel ch, HttpConnector con) throws IOException;
+
+        /**
+         * HttpChannel object no longer needed, out of pool.
+         * @throws IOException
+         */
+        public void onDestroy(HttpChannel ch, HttpConnector con) throws IOException;
+    }
+
+    private static Logger log = Logger.getLogger("HttpConnector");
+
+    /**
+     * Cache HttpChannel/request/buffers
+     */
+    private int maxHttpPoolSize = 50;
+
+    /**
+     * Max number of connections to keep alive.
+     * Each connection holds a header buffer and the socket.
+     * ( we could skip the header buffer )
+     */
+    private int maxSocketPoolSize = 500; // 10000;
+
+    private int keepAliveTimeMs = 300000;
+
+    private List<HttpChannel> httpChannelPool = new ArrayList<HttpChannel>();
+
+    protected IOConnector ioConnector;
+
+    // for https connections
+    protected SslProvider sslProvider;
+
+    boolean debugHttp = false;
+    boolean debug = false;
+
+    boolean clientKeepAlive = true;
+    boolean serverKeepAlive = true;
+
+    HttpChannelEvents httpEvents;
+
+    public AtomicInteger inUse = new AtomicInteger();
+    public AtomicInteger newHttpChannel = new AtomicInteger();
+    public AtomicInteger totalHttpChannel = new AtomicInteger();
+    public AtomicInteger totalClientHttpChannel = new AtomicInteger();
+    public AtomicInteger recycledChannels = new AtomicInteger();
+    public AtomicInteger reusedChannels = new AtomicInteger();
+
+    public HttpConnectionPool cpool = new HttpConnectionPool(this);
+
+    // Host + context mapper.
+    Dispatcher dispatcher;
+    protected HttpService defaultService;
+    int port = 8080;
+
+    private Timer timer;
+
+    boolean compression = true;
+
+    boolean serverSSL = false;
+
+    private static Timer defaultTimer = new Timer(true);
+
+    public HttpConnector(IOConnector ioConnector) {
+        this.ioConnector = ioConnector;
+        dispatcher = new Dispatcher();
+        defaultService = dispatcher;
+        if (ioConnector != null) {
+            timer = ioConnector.getTimer();
+        } else {
+            // tests
+            timer = defaultTimer;
+        }
+    }
+
+    protected HttpConnector() {
+        this(null);
+    }
+
+    public Dispatcher getDispatcher() {
+        return dispatcher;
+    }
+
+    public HttpConnectionPool getConnectionPool() {
+        return cpool;
+    }
+
+    public HttpConnector withIOConnector(IOConnector selectors) {
+        ioConnector = selectors;
+        return this;
+    }
+
+    public void setDebug(boolean b) {
+        this.debug = b;
+    }
+
+    public void setDebugHttp(boolean b) {
+        this.debugHttp  = b;
+    }
+
+    public HttpConnector withSsl(SslProvider ssl) {
+        sslProvider = ssl;
+        return this;
+    }
+
+    HttpConnector setServerSsl(boolean b) {
+        serverSSL = b;
+        return this;
+    }
+
+    public SslProvider getSslProvider() {
+        return sslProvider;
+    }
+
+    /**
+     * Allow or disable compression for this connector.
+     * Compression is enabled by default.
+     */
+    public HttpConnector setCompression(boolean b) {
+        this.compression = b;
+        return this;
+    }
+
+    public void setClientKeepAlive(boolean b) {
+        this.clientKeepAlive = b;
+    }
+
+    public void setServerKeepAlive(boolean b) {
+        this.serverKeepAlive = b;
+    }
+
+    public boolean isDebug() {
+        return debug;
+    }
+
+    public boolean isClientKeepAlive() {
+        return clientKeepAlive;
+    }
+
+    public boolean isServerKeepAlive() {
+        return serverKeepAlive;
+    }
+
+    public int getInUse() {
+        return inUse.get();
+    }
+
+    public int getMaxHttpPoolSize() {
+        return maxHttpPoolSize;
+    }
+
+    public void setMaxHttpPoolSize(int maxHttpPoolSize) {
+        this.maxHttpPoolSize = maxHttpPoolSize;
+    }
+
+    public void setOnCreate(HttpChannelEvents callback) {
+        httpEvents = callback;
+    }
+
+    /**
+     *  Override to create customized client/server connections.
+     *
+     * @return
+     * @throws IOException
+     */
+    protected HttpChannel create() throws IOException {
+        HttpChannel res = new HttpChannel();
+        newHttpChannel.incrementAndGet();
+        res.setConnector(this);
+        if (httpEvents != null) {
+            httpEvents.onCreate(res, this);
+        }
+        if (debugHttp) {
+            res.debug = debugHttp;
+        }
+        return res;
+    }
+
+    public HttpChannel get(String host, int port) throws IOException {
+        HttpChannel http = get(false);
+        http.setTarget(host + ":" + port);
+        return http;
+    }
+
+    public HttpChannel getServer() {
+        try {
+            return get(true);
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+            return null;
+        }
+    }
+
+    public HttpRequest request(String host, int port) throws IOException {
+        HttpChannel http = get(false);
+        http.setTarget(host + ":" + port);
+        return http.getRequest();
+
+    }
+
+    public HttpRequest request(CharSequence urlString) throws IOException {
+        return get(urlString).getRequest();
+    }
+
+    /**
+     * Get an existing AsyncHttp object. Since it uses many buffers and
+     * objects - it's more efficient to pool it.
+     *
+     * release will return the object to the pool.
+     * @throws IOException
+     */
+    public HttpChannel get(CharSequence urlString) throws IOException {
+        URL url = new URL(urlString.toString());
+        String host = url.getHost();
+        int port = url.getPort();
+        boolean secure = "http".equals(url.getAuthority());
+        if (port == -1) {
+            port = secure ? 443: 80;
+        }
+        // TODO: insert SSL filter
+        HttpChannel http = get(false);
+        http.setTarget(host + ":" + port);
+        String path = url.getFile(); // path + qry
+        // TODO: query string
+        http.getRequest().requestURI().set(path);
+        return http;
+    }
+
+    protected HttpChannel get(boolean server) throws IOException {
+        HttpChannel processor = null;
+        synchronized (httpChannelPool) {
+            int cnt = httpChannelPool.size();
+            if (cnt > 0) {
+                processor = httpChannelPool.remove(cnt - 1);
+            }
+        }
+        boolean reuse = false;
+        totalHttpChannel.incrementAndGet();
+        if (!server) {
+            totalClientHttpChannel.incrementAndGet();
+        }
+        if (processor == null) {
+            processor = create();
+        } else {
+            reuse = true;
+            reusedChannels.incrementAndGet();
+            processor.release = false;
+        }
+        processor.serverMode(server);
+        if (debug) {
+            log.info((reuse ? "REUSE ": "Create ") +
+                    (server? " S" : "")
+                    + " id=" + processor.ser +
+                    " " + processor +
+                    " size=" + httpChannelPool.size());
+        }
+
+        processor.setConnector(this);
+        inUse.incrementAndGet();
+        return processor;
+    }
+
+    protected void returnToPool(HttpChannel http) throws IOException {
+        inUse.decrementAndGet();
+        recycledChannels.incrementAndGet();
+        int size = 0;
+        boolean pool = false;
+
+        http.recycle();
+        http.setConnection(null);
+        http.setConnector(null);
+
+        // No more data - release the object
+        synchronized (httpChannelPool) {
+            size = httpChannelPool.size();
+            if (httpChannelPool.contains(http)) {
+                log.severe("Duplicate element in pool !");
+            } else if (size < maxHttpPoolSize) {
+                httpChannelPool.add(http);
+                pool = true;
+            }
+        }
+
+        if (!pool && httpEvents != null) {
+            httpEvents.onDestroy(http, this);
+        }
+        if (debug) {
+            log.info((pool ? "Return " : "Destroy ")
+                    + http.getTarget() + " obj=" +
+                    http + " size=" + size);
+        }
+    }
+
+
+    public IOConnector getIOConnector() {
+        return ioConnector;
+    }
+
+
+    public void setHttpService(HttpService s) {
+        defaultService = s;
+    }
+
+    public void start() throws IOException {
+        if (ioConnector != null) {
+            ioConnector.acceptor(new AcceptorCallback(),
+                    Integer.toString(port), null);
+        }
+    }
+
+    /**
+     *
+     * TODO: only clean our state and sockets we listen on.
+     *
+     */
+    public void stop() {
+        if (ioConnector != null) {
+            ioConnector.stop();
+        }
+    }
+
+    protected void connectAndSend(HttpChannel httpCh) throws IOException {
+        cpool.send(httpCh);
+
+    }
+
+    private class AcceptorCallback implements IOConnector.ConnectedCallback {
+        @Override
+        public void handleConnected(IOChannel accepted) throws IOException {
+            handleAccepted(accepted);
+        }
+    }
+
+    public HttpConnection handleAccepted(IOChannel accepted) throws IOException {
+        // TODO: reuse
+        HttpConnection shttp = cpool.accepted(accepted);
+        shttp.serverMode = true;
+
+        IOChannel head = accepted;
+        IOChannel ch;
+
+        String id = null;
+        if (debugHttp) {
+            id = port + "-" + accepted.getFirst().getAttribute(IOChannel.ATT_REMOTE_PORT);
+            log.info("Accepted " + id);
+            head = DumpChannel.wrap("SSL-" + id, head);
+        }
+
+        // TODO: seems cleaner this way...
+        if (serverSSL) {
+            ch = sslProvider.serverChannel(head);
+            head.setHead(ch);
+            head = ch;
+
+            if (debugHttp) {
+                head = DumpChannel.wrap("CLEAR-" + id, head);
+            }
+        }
+
+        shttp.setSink(head);
+
+        // Will read any data in the channel, notify data available up
+        accepted.handleReceived(accepted);
+        return shttp;
+    }
+
+    public HttpConnector setPort(int port2) {
+        this.port = port2;
+        return this;
+    }
+
+    /**
+     * Actual HTTP/1.1 wire protocol.
+     *
+     */
+    public static abstract class HttpConnection extends IOChannel
+        implements DataReceivedCallback
+    {
+        protected HttpConnector httpConnector;
+        protected boolean serverMode = false;
+
+        protected BBuffer headRecvBuf = BBuffer.allocate(8192);
+        protected CompressFilter compress = new CompressFilter();
+
+        protected boolean secure = false;
+
+        protected HttpConnectionPool.RemoteServer remoteHost;
+        // If set, the connection is in use ( active )
+        // null == keep alive. Changes synchronized on remoteHost
+        // before/after request
+        protected HttpChannel activeHttp;
+
+        @Override
+        public final void handleReceived(IOChannel ch) throws IOException {
+            int before = ch.getIn().available();
+            dataReceived(ch.getIn());
+        }
+
+        protected HttpChannel checkHttpChannel() throws IOException {
+            return null;
+        }
+
+        /**
+         * Called before a new request is sent, on a channel that is
+         * reused.
+         */
+        public void beforeRequest() {
+        }
+
+        public void setSink(IOChannel ch) throws IOException {
+            this.net = ch;
+            ch.setDataReceivedCallback(this);
+            ch.setDataFlushedCallback(this);
+            // we may have data in the buffer;
+            handleReceived(ch);
+        }
+
+
+        /**
+         * Incoming data.
+         */
+        public abstract void dataReceived(IOBuffer iob) throws IOException;
+
+        /**
+         * Framing error, client interrupt, etc.
+         */
+        public void abort(HttpChannel http, String t) throws IOException {
+        }
+
+        protected void sendRequest(HttpChannel http)
+            throws IOException {
+        }
+
+        protected void sendResponseHeaders(HttpChannel http)
+            throws IOException {
+        }
+
+        public void startSending(HttpChannel http) throws IOException {
+        }
+
+        @Override
+        public IOBuffer getIn() {
+            return net == null ? null : net.getIn();
+        }
+
+        @Override
+        public IOBuffer getOut() {
+            return net == null ? null : net.getOut();
+        }
+
+        @Override
+        public void startSending() throws IOException {
+        }
+
+        /** Called when the outgoing stream is closed:
+         * - by an explicit call to close()
+         * - when all content has been sent.
+         */
+        protected void outClosed(HttpChannel http) throws IOException {
+        }
+
+        /**
+         * Called by HttpChannel when both input and output are fully
+         * sent/received. When this happens the request is no longer associated
+         * with the Connection, and the connection can be re-used.
+         *
+         * The channel can still be used to access the retrieved data that may
+         * still be buffered until HttpChannel.release() is called.
+         *
+         * This method will be called only once, for both succesful and aborted
+         * requests.
+         */
+        protected abstract void endSendReceive(HttpChannel httpChannel) throws IOException;
+
+        public void withExtraBuffer(BBuffer received) {
+            return;
+        }
+
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java
new file mode 100644
index 0000000..5d30e63
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpMessage.java
@@ -0,0 +1,508 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.io.UnsupportedEncodingException;
+import java.io.Writer;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.BufferedIOReader;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.FastHttpDateFormat;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOInputStream;
+import org.apache.tomcat.lite.io.IOOutputStream;
+import org.apache.tomcat.lite.io.IOReader;
+import org.apache.tomcat.lite.io.IOWriter;
+import org.apache.tomcat.lite.io.UrlEncoding;
+
+
+/**
+ * Basic Http request or response message.
+ *
+ * Because the HttpChannel can be used for both client and
+ * server, and to make proxy and other code simpler - the request
+ * and response are represented by the same class.
+ *
+ * @author Costin Manolache
+ */
+public abstract class HttpMessage {
+
+    public static enum State {
+        HEAD,
+        BODY_DATA,
+        DONE
+    }
+
+    /**
+     * Raw, off-the-wire message.
+     */
+    public static class HttpMessageBytes {
+        BBuffer head1 = BBuffer.wrapper();
+        BBuffer head2 = BBuffer.wrapper();
+        BBuffer proto = BBuffer.wrapper();
+
+        BBuffer query = BBuffer.wrapper();
+
+        List<BBuffer> headerNames = new ArrayList<BBuffer>();
+        List<BBuffer> headerValues  = new ArrayList<BBuffer>();
+
+        int headerCount;
+
+        public BBuffer status() {
+            return head1;
+        }
+
+        public BBuffer method() {
+            return head1;
+        }
+
+        public BBuffer url() {
+            return head2;
+        }
+
+        public BBuffer query() {
+            return query;
+        }
+
+        public BBuffer protocol() {
+            return proto;
+        }
+
+        public BBuffer message() {
+            return head2;
+        }
+
+        public int addHeader() {
+            if (headerCount >= headerNames.size()) {
+                // make space for the new header.
+                headerNames.add(BBuffer.wrapper());
+                headerValues.add(BBuffer.wrapper());
+            }
+            return headerCount++;
+        }
+
+        public BBuffer getHeaderName(int i) {
+            if (i >= headerNames.size()) {
+                return null;
+            }
+            return headerNames.get(i);
+        }
+
+        public BBuffer getHeaderValue(int i) {
+            if (i >= headerValues.size()) {
+                return null;
+            }
+            return headerValues.get(i);
+        }
+
+        public void recycle() {
+            head1.recycle();
+            head2.recycle();
+            proto.recycle();
+            query.recycle();
+            headerCount = 0;
+            for (int i = 0; i < headerCount; i++) {
+                headerNames.get(i).recycle();
+                headerValues.get(i).recycle();
+            }
+        }
+    }
+
+    protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT");
+
+    private HttpMessageBytes msgBytes = new HttpMessageBytes();
+
+    protected HttpMessage.State state = HttpMessage.State.HEAD;
+
+    protected HttpChannel httpCh;
+
+    protected MultiMap headers = new MultiMap().insensitive();
+
+    protected CBuffer protoMB;
+
+    // Cookies
+    protected boolean cookiesParsed = false;
+
+    // TODO: cookies parsed when headers are added !
+    protected ArrayList<ServerCookie> cookies;
+    protected ArrayList<ServerCookie> cookiesCache;
+
+    protected UrlEncoding urlDecoder = new UrlEncoding();
+    protected String charEncoding;
+
+    IOReader reader;
+    BufferedIOReader bufferedReader;
+    HttpWriter writer;
+    IOWriter conv;
+
+    IOOutputStream out;
+    private IOInputStream in;
+
+    boolean commited;
+
+    protected IOBuffer body;
+
+    long contentLength = -2;
+    boolean chunked;
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     *
+     * Notice that because SimpleDateFormat is not thread-safe, we can't
+     * declare formats[] as a static variable.
+     */
+    protected SimpleDateFormat formats[] = null;
+
+
+    BBuffer clBuffer = BBuffer.allocate(64);
+
+    public HttpMessage(HttpChannel httpCh) {
+        this.httpCh = httpCh;
+
+        out = new IOOutputStream(httpCh.getOut(), httpCh);
+        conv = new IOWriter(httpCh);
+        writer = new HttpWriter(this, out, conv);
+
+        in = new IOInputStream(httpCh, httpCh.getIOTimeout());
+
+        reader = new IOReader(httpCh.getIn());
+        bufferedReader = new BufferedIOReader(reader);
+
+        cookies = new ArrayList<ServerCookie>();
+        cookiesCache = new ArrayList<ServerCookie>();
+        protoMB = CBuffer.newInstance();
+    }
+
+    public void addHeader(String name, String value) {
+        getMimeHeaders().addValue(name).set(value);
+    }
+
+    public void setHeader(String name, String value) {
+        getMimeHeaders().setValue(name).set(value);
+    }
+
+    public void setMimeHeaders(MultiMap resHeaders) {
+        this.headers = resHeaders;
+    }
+
+    public String getHeader(String name) {
+        CBuffer cb = headers.getHeader(name);
+        return (cb == null) ? null : cb.toString();
+    }
+
+    public MultiMap getMimeHeaders() {
+        return headers;
+    }
+
+    /**
+     * Return the value of the specified date header, if any; otherwise
+     * return -1.
+     *
+     * @param name Name of the requested date header
+     *
+     * @exception IllegalArgumentException if the specified header value
+     *  cannot be converted to a date
+     */
+    public long getDateHeader(String name) {
+
+        String value = getHeader(name);
+        if (value == null)
+            return (-1L);
+        if (formats == null) {
+            formats = new SimpleDateFormat[] {
+                new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+                new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+                new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+            };
+            formats[0].setTimeZone(GMT_ZONE);
+            formats[1].setTimeZone(GMT_ZONE);
+            formats[2].setTimeZone(GMT_ZONE);
+        }
+
+        // Attempt to convert the date header in a variety of formats
+        long result = FastHttpDateFormat.parseDate(value, formats);
+        if (result != (-1L)) {
+            return result;
+        }
+        throw new IllegalArgumentException(value);
+
+    }
+
+
+    public Collection<String> getHeaderNames() {
+
+        MultiMap headers = getMimeHeaders();
+        int n = headers.size();
+        ArrayList<String> result = new ArrayList<String>();
+        for (int i = 0; i < n; i++) {
+            result.add(headers.getName(i).toString());
+        }
+        return result;
+    }
+
+    public boolean containsHeader(String name) {
+        return headers.getHeader(name) != null;
+    }
+
+    public void setContentLength(long len) {
+        contentLength = len;
+        clBuffer.setLong(len);
+        setCLHeader();
+    }
+
+    public void setContentLength(int len) {
+        contentLength = len;
+        clBuffer.setLong(len);
+        setCLHeader();
+    }
+
+    private void setCLHeader() {
+        MultiMap.Entry clB = headers.setEntry("content-length");
+        clB.valueB = clBuffer;
+    }
+
+    public long getContentLengthLong() {
+        if (contentLength == -2) {
+            CBuffer clB = headers.getHeader("content-length");
+            contentLength = (clB == null) ?
+                    -1 : clB.getLong();
+        }
+        return contentLength;
+    }
+
+    public int getContentLength() {
+        long length = getContentLengthLong();
+
+        if (length < Integer.MAX_VALUE) {
+            return (int) length;
+        }
+        return -1;
+    }
+
+    public String getContentType() {
+        CBuffer contentTypeMB = headers.getHeader("content-type");
+        if (contentTypeMB == null) {
+            return null;
+        }
+        return contentTypeMB.toString();
+    }
+
+    public void setContentType(String contentType) {
+        CBuffer clB = getMimeHeaders().getHeader("content-type");
+        if (clB == null) {
+            setHeader("Content-Type", contentType);
+        } else {
+            clB.set(contentType);
+        }
+    }
+
+    /**
+     * Get the character encoding used for this request.
+     * Need a field because it can be overriden. Used to construct the
+     * Reader.
+     */
+    public String getCharacterEncoding() {
+        if (charEncoding != null)
+            return charEncoding;
+
+        charEncoding = ContentType.getCharsetFromContentType(getContentType());
+        return charEncoding;
+    }
+
+    private static final String DEFAULT_ENCODING = "ISO-8859-1";
+
+    public String getEncoding() {
+        String charEncoding = getCharacterEncoding();
+        if (charEncoding == null) {
+            return DEFAULT_ENCODING;
+        } else {
+            return charEncoding;
+        }
+    }
+
+    public void setCharacterEncoding(String enc)
+            throws UnsupportedEncodingException {
+        this.charEncoding = enc;
+    }
+
+
+    public void recycle() {
+        commited = false;
+        headers.recycle();
+        protoMB.set("HTTP/1.1");
+        for (int i = 0; i < cookies.size(); i++) {
+            cookies.get(i).recycle();
+        }
+        cookies.clear();
+        charEncoding = null;
+        bufferedReader.recycle();
+
+        writer.recycle();
+        conv.recycle();
+
+        contentLength = -2;
+        chunked = false;
+        clBuffer.recycle();
+        state = State.HEAD;
+        cookiesParsed = false;
+        getMsgBytes().recycle();
+
+    }
+
+
+    public String getProtocol() {
+        return protoMB.toString();
+    }
+
+    public void setProtocol(String proto) {
+        protoMB.set(proto);
+    }
+
+    public CBuffer protocol() {
+        return protoMB;
+    }
+
+    public ServerCookie getCookie(String name) {
+        for (ServerCookie sc: getServerCookies()) {
+            if (sc.getName().equalsIgnoreCase(name)) {
+                return sc;
+            }
+        }
+        return null;
+    }
+
+    public List<ServerCookie> getServerCookies() {
+        if (!cookiesParsed) {
+            cookiesParsed = true;
+            ServerCookie.processCookies(cookies, cookiesCache, getMsgBytes());
+        }
+        return cookies;
+    }
+
+    public UrlEncoding getURLDecoder() {
+        return urlDecoder;
+    }
+
+    public boolean isCommitted() {
+        return commited;
+    }
+
+    public void setCommitted(boolean b) {
+        commited = b;
+    }
+
+    public HttpChannel getHttpChannel() {
+        return httpCh;
+    }
+
+    public IOBuffer getBody() {
+        return body;
+    }
+
+    void setBody(IOBuffer body) {
+        this.body = body;
+    }
+
+    public void flush() throws IOException {
+        httpCh.startSending();
+    }
+
+    // not servlet input stream
+    public IOInputStream getBodyInputStream() {
+        return in;
+    }
+
+    public InputStream getInputStream() {
+        return in;
+    }
+
+    public IOOutputStream getOutputStream() {
+        return out;
+    }
+
+    public IOOutputStream getBodyOutputStream() {
+        return out;
+    }
+
+    public IOReader getBodyReader() throws IOException {
+        reader.setEncoding(getCharacterEncoding());
+        return reader;
+    }
+
+    public BBuffer readAll(BBuffer chunk, long to) throws IOException {
+        return httpCh.readAll(chunk, to);
+    }
+
+    public BBuffer readAll() throws IOException {
+        return httpCh.readAll(null, httpCh.ioTimeout);
+    }
+
+    /**
+     * We're done with this object, it can be recycled.
+     * Any use after this should throw exception or affect an
+     *  unrelated request.
+     */
+    public void release() throws IOException {
+        httpCh.release();
+    }
+
+    public void setCompletedCallback(RequestCompleted doneAllCallback) throws IOException {
+        httpCh.setCompletedCallback(doneAllCallback);
+    }
+
+    public void setReadTimeout(long to) {
+        reader.setTimeout(to);
+    }
+
+    /**
+     * Returns a buffered reader.
+     */
+    public BufferedReader getReader() throws IOException {
+        reader.setEncoding(getCharacterEncoding());
+        return bufferedReader;
+    }
+
+    public PrintWriter getWriter() {
+        return new PrintWriter(getBodyWriter());
+    }
+
+    public HttpWriter getBodyWriter() {
+        conv.setEncoding(getCharacterEncoding());
+        return writer;
+    }
+
+
+    protected void processMimeHeaders() {
+        for (int idx = 0; idx < getMsgBytes().headerCount; idx++) {
+            BBuffer nameBuf = getMsgBytes().getHeaderName(idx);
+            BBuffer valBuf = getMsgBytes().getHeaderValue(idx);
+
+            MultiMap.Entry header = headers.addEntry(nameBuf);
+            header.valueB = valBuf;
+        }
+    }
+
+
+    protected abstract void processReceivedHeaders() throws IOException;
+
+    public abstract boolean hasBody();
+
+    public HttpMessageBytes getMsgBytes() {
+        // TODO: serialize if not set
+        return msgBytes;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java
new file mode 100644
index 0000000..be934dd
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpRequest.java
@@ -0,0 +1,1019 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.MultiMap.Entry;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.Hex;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOReader;
+import org.apache.tomcat.lite.io.IOWriter;
+import org.apache.tomcat.lite.io.UrlEncoding;
+
+public class HttpRequest extends HttpMessage {
+    public static final String DEFAULT_CHARACTER_ENCODING="ISO-8859-1";
+
+    protected CBuffer schemeMB;
+    protected CBuffer methodMB;
+    protected CBuffer remoteAddrMB;
+    protected CBuffer remoteHostMB;
+    protected int remotePort;
+
+    protected CBuffer localNameMB;
+    protected CBuffer localAddrMB;
+    protected int localPort = -1;
+
+    // Host: header, or default:80
+    protected CBuffer serverNameMB;
+    protected int serverPort = -1;
+
+
+    // ==== Derived fields, computed after request is received ===
+
+    protected CBuffer requestURI;
+    protected CBuffer queryMB;
+
+    protected BBuffer decodedUri = BBuffer.allocate();
+    protected CBuffer decodedUriMB;
+
+    // Decoded query
+    protected MultiMap parameters;
+
+    boolean parametersParsed = false;
+
+    protected IOWriter charEncoder = new IOWriter(null);
+    protected IOReader charDecoder = new IOReader(null);
+    protected UrlEncoding urlEncoding = new UrlEncoding();
+
+    // Reference to 'real' request object
+    // will not be recycled
+    public Object nativeRequest;
+    public Object wrapperRequest;
+
+    boolean ssl = false;
+
+    boolean async = false;
+
+    CBuffer requestURL = CBuffer.newInstance();
+
+    private Map<String, Object> attributes = new HashMap<String, Object>();
+
+    /**
+     * Mapping data.
+     */
+    protected MappingData mappingData = new MappingData();
+
+
+    HttpRequest(HttpChannel httpCh) {
+        super(httpCh);
+        decodedUriMB = CBuffer.newInstance();
+        requestURI = CBuffer.newInstance();
+        queryMB = CBuffer.newInstance();
+        serverNameMB = CBuffer.newInstance();
+
+        parameters = new MultiMap();
+
+        schemeMB =
+            CBuffer.newInstance();
+        methodMB = CBuffer.newInstance();
+        initRemote();
+    }
+
+    protected void initRemote() {
+        remoteAddrMB = CBuffer.newInstance();
+        localNameMB = CBuffer.newInstance();
+        remoteHostMB = CBuffer.newInstance();
+        localAddrMB = CBuffer.newInstance();
+    }
+
+    public void recycle() {
+        super.recycle();
+        schemeMB.recycle();
+        methodMB.set("GET");
+        requestURI.recycle();
+        requestURL.recycle();
+        queryMB.recycle();
+        decodedUriMB.recycle();
+
+        parameters.recycle();
+        remoteAddrMB.recycle();
+        remoteHostMB.recycle();
+        parametersParsed = false;
+        ssl = false;
+        async = false;
+        asyncTimeout = -1;
+        charEncoder.recycle();
+
+        localPort = -1;
+        remotePort = -1;
+        localAddrMB.recycle();
+        localNameMB.recycle();
+
+        serverPort = -1;
+        serverNameMB.recycle();
+        decodedUri.recycle();
+        decodedQuery.recycle();
+    }
+
+    public Object getAttribute(String name) {
+        return attributes.get(name);
+    }
+
+    public void setAttribute(String name, Object o) {
+        if (o == null) {
+            attributes.remove(name);
+        } else {
+            attributes.put(name, o);
+        }
+    }
+    // getAttributeNames not supported
+
+    public Map<String, Object> attributes() {
+        return attributes;
+    }
+
+
+    public CBuffer method() {
+        return methodMB;
+    }
+
+    public String getMethod() {
+        return methodMB.toString();
+    }
+
+    public void setMethod(String method) {
+        methodMB.set(method);
+    }
+
+    public CBuffer scheme() {
+        return schemeMB;
+    }
+
+    public String getScheme() {
+        String scheme = schemeMB.toString();
+        if (scheme == null) {
+            return "http";
+        }
+        return scheme;
+    }
+
+    public void setScheme(String s) {
+        schemeMB.set(s);
+    }
+
+    public MappingData getMappingData() {
+        return (mappingData);
+    }
+
+    /**
+     * Return the portion of the request URI used to select the Context
+     * of the Request.
+     */
+    public String getContextPath() {
+        return (getMappingData().contextPath.toString());
+    }
+
+    public String getPathInfo() {
+        CBuffer pathInfo = getMappingData().pathInfo;
+        if (pathInfo.length() == 0) {
+            return null;
+        }
+        return (getMappingData().pathInfo.toString());
+    }
+
+    /**
+     * Return the portion of the request URI used to select the servlet
+     * that will process this request.
+     */
+    public String getServletPath() {
+        return (getMappingData().wrapperPath.toString());
+    }
+
+    /**
+     * Parse query parameters - but not POST body.
+     *
+     * If you don't call this method, getParameters() will
+     * also read the body for POST with x-www-url-encoded
+     * mime type.
+     */
+    public void parseQueryParameters() {
+        parseQuery();
+    }
+
+    /**
+     * Explicitely parse the body, adding the parameters to
+     * those from the query ( if already parsed ).
+     *
+     * By default servlet mode ( both query and body ) is used.
+     */
+    public void parsePostParameters() {
+        parseBody();
+    }
+
+    MultiMap getParameters() {
+        if (!parametersParsed) {
+            parseQuery();
+            parseBody();
+        }
+        return parameters;
+    }
+
+    public Enumeration<String> getParameterNames() {
+        return getParameters().names();
+    }
+
+    /**
+     * Expensive, creates a copy on each call.
+     * @param name
+     * @return
+     */
+    public String[] getParameterValues(String name) {
+        Entry entry = getParameters().getEntry(name);
+        if (entry == null) {
+            return null;
+        }
+        String[] values = new String[entry.values.size()];
+        for (int j = 0; j < values.length; j++) {
+            values[j] = entry.values.get(j).toString();
+        }
+        return values;
+    }
+
+    // Inefficient - we convert from a different representation.
+    public Map<String, String[]> getParameterMap() {
+        // we could allow 'locking' - I don't think this is
+        // a very useful optimization
+        Map<String, String[]> map = new HashMap();
+        for (int i = 0; i < getParameters().size(); i++) {
+            Entry entry = getParameters().getEntry(i);
+            if (entry == null) {
+                continue;
+            }
+            if (entry.key == null) {
+                continue;
+            }
+            String name = entry.key.toString();
+            String[] values = new String[entry.values.size()];
+            for (int j = 0; j < values.length; j++) {
+                values[j] = entry.values.get(j).toString();
+            }
+            map.put(name, values);
+        }
+        return map;
+    }
+
+    public String getParameter(String name) {
+        CharSequence value = getParameters().get(name);
+        if (value == null) {
+            return null;
+        }
+        return value.toString();
+    }
+
+    public void setParameter(String name, String value) {
+        getParameters().set(name, value);
+    }
+
+    public void addParameter(String name, String values) {
+        getParameters().add(name, values);
+    }
+
+    public CBuffer queryString() {
+        return queryMB;
+    }
+
+    // TODO
+    void serializeParameters(Appendable cc) throws IOException {
+        int keys = parameters.size();
+        boolean notFirst = false;
+        for (int i = 0; i < parameters.size(); i++) {
+            Entry entry = parameters.getEntry(i);
+            for (int j = 0; j < entry.values.size(); j++) {
+                // TODO: Uencode
+                if (notFirst) {
+                    cc.append('&');
+                } else {
+                    notFirst = true;
+                }
+                cc.append(entry.key);
+                cc.append("=");
+                cc.append(entry.values.get(j).getValue());
+            }
+        }
+    }
+
+    public void setURI(CharSequence encoded) {
+        decodedUriMB.recycle();
+        decodedUriMB.append(encoded);
+        // TODO: generate % encoding ( reverse of decodeRequest )
+    }
+
+    public CBuffer decodedURI() {
+        return decodedUriMB;
+    }
+
+    public CBuffer requestURI() {
+        return requestURI;
+    }
+
+    public CBuffer requestURL() {
+        CBuffer url = requestURL;
+        url.recycle();
+
+        String scheme = getScheme();
+        int port = getServerPort();
+        if (port < 0)
+            port = 80; // Work around java.net.URL bug
+
+        url.append(scheme);
+        url.append("://");
+        url.append(getServerName());
+        if ((scheme.equals("http") && (port != 80))
+            || (scheme.equals("https") && (port != 443))) {
+            url.append(':');
+            url.append(port);
+        }
+        // Decoded !!
+        url.append(getRequestURI());
+
+        return (url);
+
+    }
+
+    /**
+     * Not decoded - %xx as in original.
+     * @return
+     */
+    public String getRequestURI() {
+        return requestURI.toString();
+    }
+
+    public void setRequestURI(String encodedUri) {
+        requestURI.set(encodedUri);
+    }
+
+    CBuffer getOrAdd(String name) {
+        CBuffer header = getMimeHeaders().getHeader(name);
+        if (header == null) {
+            header = getMimeHeaders().addValue(name);
+        }
+        return header;
+    }
+
+    /**
+     * Set the Host header of the request.
+     * @param target
+     */
+    public void setHost(String target) {
+        serverNameMB.recycle();
+        getOrAdd("Host").set(target);
+    }
+
+    // XXX
+    public CBuffer serverName() {
+        if (serverNameMB.length() == 0) {
+            parseHost();
+        }
+        return serverNameMB;
+    }
+
+    public String getServerName() {
+        return serverName().toString();
+    }
+
+    public void setServerName(String name)  {
+        serverName().set(name);
+    }
+
+    public int getServerPort() {
+        serverName();
+        return serverPort;
+    }
+
+    public void setServerPort(int serverPort ) {
+        this.serverPort=serverPort;
+    }
+
+    public CBuffer remoteAddr() {
+        if (remoteAddrMB.length() == 0) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            remoteAddrMB.set((String)
+                    iochannel.getAttribute(IOChannel.ATT_REMOTE_ADDRESS));
+        }
+        return remoteAddrMB;
+    }
+
+    public CBuffer remoteHost() {
+        if (remoteHostMB.length() == 0) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            remoteHostMB.set((String)
+                    iochannel.getAttribute(IOChannel.ATT_REMOTE_HOSTNAME));
+        }
+        return remoteHostMB;
+    }
+
+    public CBuffer localName() {
+        return localNameMB;
+    }
+
+    public CBuffer localAddr() {
+        return localAddrMB;
+    }
+
+    public int getRemotePort(){
+        if (remotePort == -1) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            remotePort = (Integer) iochannel.getAttribute(IOChannel.ATT_REMOTE_PORT);
+        }
+        return remotePort;
+    }
+
+    public void setRemotePort(int port){
+        this.remotePort = port;
+    }
+
+    public int getLocalPort(){
+        if (localPort == -1) {
+            HttpChannel asyncHttp = getHttpChannel();
+            IOChannel iochannel = asyncHttp.getNet().getFirst();
+            localPort = (Integer) iochannel.getAttribute(IOChannel.ATT_LOCAL_PORT);
+        }
+        return localPort;
+    }
+
+    public void setLocalPort(int port){
+        this.localPort = port;
+    }
+
+    public HttpResponse waitResponse() throws IOException {
+        return waitResponse(httpCh.ioTimeout);
+    }
+
+    public void send(HttpService headersCallback, long timeout) throws IOException {
+        if (headersCallback != null) {
+            httpCh.setHttpService(headersCallback);
+        }
+
+        httpCh.send();
+    }
+
+    public void send(HttpService headersCallback) throws IOException {
+        send(headersCallback, httpCh.ioTimeout);
+    }
+
+    public void send() throws IOException {
+        send(null, httpCh.ioTimeout);
+    }
+
+    public HttpResponse waitResponse(long timeout) throws IOException {
+        // TODO: close out if post
+        httpCh.send();
+
+        httpCh.headersReceivedLock.waitSignal(timeout);
+
+        return httpCh.getResponse();
+    }
+
+    /**
+     * Parse host.
+     * @param serverNameMB2
+     * @throws IOException
+     */
+    boolean parseHost()  {
+        MultiMap.Entry hostHF = getMimeHeaders().getEntry("Host");
+        if (hostHF == null) {
+            // HTTP/1.0
+            // Default is what the socket tells us. Overriden if a host is
+            // found/parsed
+            return true;
+        }
+
+        BBuffer valueBC = hostHF.valueB;
+        if (valueBC == null) {
+            valueBC = BBuffer.allocate();
+            hostHF.getValue().toAscii(valueBC);
+        }
+        byte[] valueB = valueBC.array();
+        int valueL = valueBC.getLength();
+        int valueS = valueBC.getStart();
+
+        int colonPos = valueBC.indexOf(':', 0);
+
+        serverNameMB.recycle();
+
+        boolean ipv6 = (valueB[valueS] == '[');
+        boolean bracketClosed = false;
+        for (int i = 0; i < valueL; i++) {
+            char b = (char) valueB[i + valueS];
+            if (b == ':') {
+                if (!ipv6 || bracketClosed) {
+                    colonPos = i;
+                    break;
+                }
+            }
+            serverNameMB.append(b);
+            if (b == ']') {
+                bracketClosed = true;
+            }
+        }
+
+        if (colonPos < 0) {
+            if (!ssl) {
+                setServerPort(80);
+            } else {
+                setServerPort(443);
+            }
+        } else {
+            int port = 0;
+            int mult = 1;
+            for (int i = valueL - 1; i > colonPos; i--) {
+                int charValue = Hex.DEC[(int) valueB[i + valueS]];
+                if (charValue == -1) {
+                    // we don't return 400 - could do it
+                    return false;
+                }
+                port = port + (charValue * mult);
+                mult = 10 * mult;
+            }
+            setServerPort(port);
+
+        }
+        return true;
+    }
+
+    // TODO: this is from coyote - MUST be rewritten !!!
+    // - cleaner
+    // - chunked encoding for body
+    // - buffer should be in a pool, etc.
+    /**
+     * Post data buffer.
+     */
+    public final static int CACHED_POST_LEN = 8192;
+
+    public  byte[] postData = null;
+
+    private long asyncTimeout = -1;
+
+    /**
+     * Parse request parameters.
+     */
+    protected void parseQuery() {
+
+        parametersParsed = true;
+
+        // getCharacterEncoding() may have been overridden to search for
+        // hidden form field containing request encoding
+        String enc = getEncoding();
+
+//        boolean useBodyEncodingForURI = connector.getUseBodyEncodingForURI();
+//        if (enc != null) {
+//            parameters.setEncoding(enc);
+////            if (useBodyEncodingForURI) {
+////                parameters.setQueryStringEncoding(enc);
+////            }
+//        } else {
+//            parameters.setEncoding(DEFAULT_CHARACTER_ENCODING);
+////            if (useBodyEncodingForURI) {
+////                parameters.setQueryStringEncoding
+////                    (DEFAULT_CHARACTER_ENCODING);
+////            }
+//        }
+
+        handleQueryParameters();
+    }
+
+    // Copy - will be modified by decoding
+    BBuffer decodedQuery = BBuffer.allocate(1024);
+
+    CBuffer tmpNameC = CBuffer.newInstance();
+    BBuffer tmpName = BBuffer.wrapper();
+    BBuffer tmpValue = BBuffer.wrapper();
+
+    CBuffer tmpNameCB = CBuffer.newInstance();
+    CBuffer tmpValueCB = CBuffer.newInstance();
+
+    /**
+     * Process the query string into parameters
+     */
+    public void handleQueryParameters() {
+        if( queryMB.length() == 0) {
+            return;
+        }
+
+        decodedQuery.recycle();
+        decodedQuery.append(getMsgBytes().query());
+        // TODO: option 'useBodyEncodingForUri' - versus UTF or ASCII
+        String queryStringEncoding = getEncoding();
+        processParameters( decodedQuery, queryStringEncoding );
+    }
+
+    public void processParameters( BBuffer bc, String encoding ) {
+        if( bc.isNull())
+            return;
+        if (bc.remaining() ==0) {
+            return;
+        }
+        processParameters( bc.array(), bc.getOffset(),
+                           bc.getLength(), encoding);
+    }
+
+    public void processParameters( byte bytes[], int start, int len,
+            String enc ) {
+        int end=start+len;
+        int pos=start;
+
+        do {
+            boolean noEq=false;
+            int valStart=-1;
+            int valEnd=-1;
+
+            int nameStart=pos;
+            int nameEnd=BBuffer.indexOf(bytes, nameStart, end, '=' );
+            // Workaround for a&b&c encoding
+            int nameEnd2=BBuffer.indexOf(bytes, nameStart, end, '&' );
+            if( (nameEnd2!=-1 ) &&
+                    ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+                nameEnd=nameEnd2;
+                noEq=true;
+                valStart=nameEnd;
+                valEnd=nameEnd;
+            }
+            if( nameEnd== -1 )
+                nameEnd=end;
+
+            if( ! noEq ) {
+                valStart= (nameEnd < end) ? nameEnd+1 : end;
+                valEnd=BBuffer.indexOf(bytes, valStart, end, '&');
+                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+            }
+
+            pos=valEnd+1;
+
+            if( nameEnd<=nameStart ) {
+                // No name eg ...&=xx&... will trigger this
+                continue;
+            }
+
+            // TODO: use CBuffer, recycle
+            tmpName.setBytes( bytes, nameStart, nameEnd-nameStart );
+            tmpValue.setBytes( bytes, valStart, valEnd-valStart );
+
+            try {
+                parameters.add(urlDecode(tmpName, enc),
+                        urlDecode(tmpValue, enc));
+            } catch (IOException e) {
+                // ignored
+            }
+        } while( pos<end );
+    }
+
+//    public void processParameters(char bytes[], int start, int len,
+//            String enc ) {
+//        int end=start+len;
+//        int pos=start;
+//
+//        do {
+//            boolean noEq=false;
+//            int valStart=-1;
+//            int valEnd=-1;
+//
+//            int nameStart=pos;
+//            int nameEnd=CBuffer.indexOf(bytes, nameStart, end, '=' );
+//            // Workaround for a&b&c encoding
+//            int nameEnd2=CBuffer.indexOf(bytes, nameStart, end, '&' );
+//            if( (nameEnd2!=-1 ) &&
+//                    ( nameEnd==-1 || nameEnd > nameEnd2) ) {
+//                nameEnd=nameEnd2;
+//                noEq=true;
+//                valStart=nameEnd;
+//                valEnd=nameEnd;
+//            }
+//            if( nameEnd== -1 )
+//                nameEnd=end;
+//
+//            if( ! noEq ) {
+//                valStart= (nameEnd < end) ? nameEnd+1 : end;
+//                valEnd=CBuffer.indexOf(bytes, valStart, end, '&');
+//                if( valEnd== -1 ) valEnd = (valStart < end) ? end : valStart;
+//            }
+//
+//            pos=valEnd+1;
+//
+//            if( nameEnd<=nameStart ) {
+//                // No name eg ...&=xx&... will trigger this
+//                continue;
+//            }
+//
+//            // TODO: use CBuffer, recycle
+//            tmpNameCB.recycle();
+//            tmpValueCB.recycle();
+//
+//            tmpNameCB.wrap( bytes, nameStart, nameEnd );
+//            tmpValueCB.wrap( bytes, valStart, valEnd );
+//
+//            //CharChunk name = new CharChunk();
+//            //CharChunk value = new CharChunk();
+//            // TODO:
+//            try {
+//                parameters.add(urlDecode(tmpName, enc),
+//                        urlDecode(tmpValue, enc));
+//            } catch (IOException e) {
+//                // ignored
+//            }
+//        } while( pos<end );
+//    }
+
+    private String urlDecode(BBuffer bc, String enc)
+            throws IOException {
+        // Replace %xx
+        urlDecoder.urlDecode(bc, true);
+
+        String result = null;
+        if (enc != null) {
+            result = bc.toString(enc);
+        } else {
+            // Ascii
+
+            CBuffer cc = tmpNameC;
+            cc.recycle();
+            int length = bc.getLength();
+            byte[] bbuf = bc.array();
+            int start = bc.getStart();
+            cc.appendAscii(bbuf, start, length);
+            result = cc.toString();
+            cc.recycle();
+        }
+        return result;
+    }
+
+    private void processParameters( byte bytes[], int start, int len ) {
+        processParameters(bytes, start, len, getEncoding());
+    }
+
+    protected void parseBody() {
+
+        parametersParsed = true;
+        String enc = getCharacterEncoding();
+
+//      if (usingInputStream || usingReader)
+//      return;
+        if (!getMethod().equalsIgnoreCase("POST"))
+            return;
+
+        String contentType = getContentType();
+        if (contentType == null)
+            contentType = "";
+        int semicolon = contentType.indexOf(';');
+        if (semicolon >= 0) {
+            contentType = contentType.substring(0, semicolon).trim();
+        } else {
+            contentType = contentType.trim();
+        }
+        if (!("application/x-www-form-urlencoded".equals(contentType)))
+            return;
+
+        int len = getContentLength();
+
+        if (len > 0) {
+            try {
+                byte[] formData = null;
+                if (len < CACHED_POST_LEN) {
+                    if (postData == null)
+                        postData = new byte[CACHED_POST_LEN];
+                    formData = postData;
+                } else {
+                    formData = new byte[len];
+                }
+                int actualLen = readPostBody(formData, len);
+                if (actualLen == len) {
+                    processParameters(formData, 0, len);
+                }
+            } catch (Throwable t) {
+                ; // Ignore
+            }
+        }
+
+    }
+
+    /**
+     * Read post body in an array.
+     */
+    protected int readPostBody(byte body[], int len)
+        throws IOException {
+
+        int offset = 0;
+        do {
+            int inputLen = getBodyInputStream().read(body, offset, len - offset);
+            if (inputLen <= 0) {
+                return offset;
+            }
+            offset += inputLen;
+        } while ((len - offset) > 0);
+        return len;
+
+    }
+
+    // Async support - a subset of servlet spec, the fancy stuff is in the
+    // facade.
+
+    public boolean isAsyncStarted() {
+        return async;
+    }
+
+    public void async() {
+        this.async = true;
+    }
+
+    public void setAsyncTimeout(long timeout) {
+        this.asyncTimeout  = timeout;
+    }
+
+    /**
+     * Server mode, request just received.
+     */
+    protected void processReceivedHeaders() throws IOException {
+        BBuffer url = getMsgBytes().url();
+        if (url.remaining() == 0) {
+            System.err.println("No input");
+        }
+        if (url.get(0) == 'h') {
+            int firstSlash = url.indexOf('/', 0);
+            schemeMB.appendAscii(url.array(),
+                    url.getStart(), firstSlash + 2);
+            if (!schemeMB.equals("http://") &&
+                    !schemeMB.equals("https://")) {
+                httpCh.getResponse().setStatus(400);
+                httpCh.abort("Error normalizing url " +
+                        getMsgBytes().url());
+                return;
+            }
+
+            int urlStart = url.indexOf('/', firstSlash + 2);
+            serverNameMB.recycle();
+            serverNameMB.appendAscii(url.array(),
+                    url.getStart() + firstSlash + 2, urlStart - firstSlash - 2);
+
+            url.position(url.getStart() + urlStart);
+        }
+        if (!httpCh.normalize(getMsgBytes().url())) {
+            httpCh.getResponse().setStatus(400);
+            httpCh.abort("Error normalizing url " +
+                    getMsgBytes().url());
+            return;
+        }
+
+        method().set(getMsgBytes().method());
+        requestURI().set(getMsgBytes().url());
+        queryString().set(getMsgBytes().query());
+        protocol().set(getMsgBytes().protocol());
+
+        processMimeHeaders();
+
+        // URL decode and normalize
+        decodedUri.append(getMsgBytes().url());
+
+        getURLDecoder().urlDecode(decodedUri, false);
+
+        // Need to normalize again - %decoding may decode /
+        if (!httpCh.normalize(decodedUri)) {
+            httpCh.getResponse().setStatus(400);
+            httpCh.abort("Invalid decoded uri " + decodedUri);
+            return;
+        }
+        decodedURI().set(decodedUri);
+
+        // default response protocol
+        httpCh.getResponse().protocol().set(getMsgBytes().protocol());
+    }
+
+
+    public boolean hasBody() {
+        return chunked || contentLength >= 0;
+    }
+
+    /**
+     * Convert (if necessary) and return the absolute URL that represents the
+     * resource referenced by this possibly relative URL.  If this URL is
+     * already absolute, return it unchanged.
+     *
+     * @param location URL to be (possibly) converted and then returned
+     *
+     * @exception IllegalArgumentException if a MalformedURLException is
+     *  thrown when converting the relative URL to an absolute one
+     */
+    public void toAbsolute(String location, CBuffer cb) {
+
+        cb.recycle();
+        if (location == null)
+            return;
+
+        boolean leadingSlash = location.startsWith("/");
+        if (leadingSlash || !hasScheme(location)) {
+
+            String scheme = getScheme();
+            String name = serverName().toString();
+            int port = getServerPort();
+
+            cb.append(scheme);
+            cb.append("://", 0, 3);
+            cb.append(name);
+            if ((scheme.equals("http") && port != 80)
+                    || (scheme.equals("https") && port != 443)) {
+                cb.append(':');
+                String portS = port + "";
+                cb.append(portS);
+            }
+            if (!leadingSlash) {
+                String relativePath = decodedURI().toString();
+                int pos = relativePath.lastIndexOf('/');
+                relativePath = relativePath.substring(0, pos);
+
+                //String encodedURI = null;
+                urlEncoding.urlEncode(relativePath,  cb, charEncoder);
+                //encodedURI = urlEncoder.encodeURL(relativePath);
+                //redirectURLCC.append(encodedURI, 0, encodedURI.length());
+                cb.append('/');
+            }
+
+            cb.append(location);
+        } else {
+            cb.append(location);
+        }
+
+    }
+
+    /**
+     * Determine if a URI string has a <code>scheme</code> component.
+     */
+    public static boolean hasScheme(String uri) {
+        int len = uri.length();
+        for(int i=0; i < len ; i++) {
+            char c = uri.charAt(i);
+            if(c == ':') {
+                return i > 0;
+            } else if(!isSchemeChar(c)) {
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Determine if the character is allowed in the scheme of a URI.
+     * See RFC 2396, Section 3.1
+     */
+    private static boolean isSchemeChar(char c) {
+        return Character.isLetterOrDigit(c) ||
+            c == '+' || c == '-' || c == '.';
+    }
+
+    public IOWriter getCharEncoder() {
+        return charEncoder;
+    }
+
+    public IOReader getCharDecoder() {
+        return charDecoder;
+    }
+
+    public UrlEncoding getUrlEncoding() {
+        return urlEncoding;
+    }
+
+    public BBuffer toBytes(CBuffer cb, BBuffer bb) {
+        if (bb == null) {
+            bb = BBuffer.allocate(cb.length());
+        }
+        getCharEncoder().encodeAll(cb, bb, "UTF-8");
+        return bb;
+    }
+
+    public String toString() {
+        IOBuffer out = new IOBuffer();
+        try {
+            Http11Connection.serialize(this, out);
+            return out.readAll(null).toString();
+        } catch (IOException e) {
+            return "Invalid request";
+        }
+    }
+
+    public boolean isSecure() {
+        return ssl;
+    }
+
+    public HttpRequest setSecure(boolean ssl) {
+        this.ssl = ssl;
+        return this;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java
new file mode 100644
index 0000000..d616c1c
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpResponse.java
@@ -0,0 +1,581 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.HashMap;
+
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+
+public class HttpResponse extends HttpMessage {
+
+    /*
+     * Server status codes; see RFC 2068.
+     */
+
+    /**
+     * Status code (100) indicating the client can continue.
+     */
+
+    public static final int SC_CONTINUE = 100;
+
+
+    /**
+     * Status code (101) indicating the server is switching protocols
+     * according to Upgrade header.
+     */
+
+    public static final int SC_SWITCHING_PROTOCOLS = 101;
+
+    /**
+     * Status code (200) indicating the request succeeded normally.
+     */
+
+    public static final int SC_OK = 200;
+
+    /**
+     * Status code (201) indicating the request succeeded and created
+     * a new resource on the server.
+     */
+
+    public static final int SC_CREATED = 201;
+
+    /**
+     * Status code (202) indicating that a request was accepted for
+     * processing, but was not completed.
+     */
+
+    public static final int SC_ACCEPTED = 202;
+
+    /**
+     * Status code (203) indicating that the meta information presented
+     * by the client did not originate from the server.
+     */
+
+    public static final int SC_NON_AUTHORITATIVE_INFORMATION = 203;
+
+    /**
+     * Status code (204) indicating that the request succeeded but that
+     * there was no new information to return.
+     */
+
+    public static final int SC_NO_CONTENT = 204;
+
+    /**
+     * Status code (205) indicating that the agent <em>SHOULD</em> reset
+     * the document view which caused the request to be sent.
+     */
+
+    public static final int SC_RESET_CONTENT = 205;
+
+    /**
+     * Status code (206) indicating that the server has fulfilled
+     * the partial GET request for the resource.
+     */
+
+    public static final int SC_PARTIAL_CONTENT = 206;
+
+    /**
+     * Used by Webdav.
+     */
+    public static final int SC_MULTI_STATUS = 207;
+    // This one collides with HTTP 1.1
+    // "207 Partial Update OK"
+
+    /**
+     * Status code (300) indicating that the requested resource
+     * corresponds to any one of a set of representations, each with
+     * its own specific location.
+     */
+
+    public static final int SC_MULTIPLE_CHOICES = 300;
+
+    /**
+     * Status code (301) indicating that the resource has permanently
+     * moved to a new location, and that future references should use a
+     * new URI with their requests.
+     */
+
+    public static final int SC_MOVED_PERMANENTLY = 301;
+
+    /**
+     * Status code (302) indicating that the resource has temporarily
+     * moved to another location, but that future references should
+     * still use the original URI to access the resource.
+     *
+     * This definition is being retained for backwards compatibility.
+     * SC_FOUND is now the preferred definition.
+     */
+
+    public static final int SC_MOVED_TEMPORARILY = 302;
+
+    /**
+    * Status code (302) indicating that the resource reside
+    * temporarily under a different URI. Since the redirection might
+    * be altered on occasion, the client should continue to use the
+    * Request-URI for future requests.(HTTP/1.1) To represent the
+    * status code (302), it is recommended to use this variable.
+    */
+
+    public static final int SC_FOUND = 302;
+
+    /**
+     * Status code (303) indicating that the response to the request
+     * can be found under a different URI.
+     */
+
+    public static final int SC_SEE_OTHER = 303;
+
+    /**
+     * Status code (304) indicating that a conditional GET operation
+     * found that the resource was available and not modified.
+     */
+
+    public static final int SC_NOT_MODIFIED = 304;
+
+    /**
+     * Status code (305) indicating that the requested resource
+     * <em>MUST</em> be accessed through the proxy given by the
+     * <code><em>Location</em></code> field.
+     */
+
+    public static final int SC_USE_PROXY = 305;
+
+     /**
+     * Status code (307) indicating that the requested resource
+     * resides temporarily under a different URI. The temporary URI
+     * <em>SHOULD</em> be given by the <code><em>Location</em></code>
+     * field in the response.
+     */
+
+     public static final int SC_TEMPORARY_REDIRECT = 307;
+
+    /**
+     * Status code (400) indicating the request sent by the client was
+     * syntactically incorrect.
+     */
+
+    public static final int SC_BAD_REQUEST = 400;
+
+    /**
+     * Status code (401) indicating that the request requires HTTP
+     * authentication.
+     */
+
+    public static final int SC_UNAUTHORIZED = 401;
+
+    /**
+     * Status code (402) reserved for future use.
+     */
+
+    public static final int SC_PAYMENT_REQUIRED = 402;
+
+    /**
+     * Status code (403) indicating the server understood the request
+     * but refused to fulfill it.
+     */
+
+    public static final int SC_FORBIDDEN = 403;
+
+    /**
+     * Status code (404) indicating that the requested resource is not
+     * available.
+     */
+
+    public static final int SC_NOT_FOUND = 404;
+
+    /**
+     * Status code (405) indicating that the method specified in the
+     * <code><em>Request-Line</em></code> is not allowed for the resource
+     * identified by the <code><em>Request-URI</em></code>.
+     */
+
+    public static final int SC_METHOD_NOT_ALLOWED = 405;
+
+    /**
+     * Status code (406) indicating that the resource identified by the
+     * request is only capable of generating response entities which have
+     * content characteristics not acceptable according to the accept
+     * headers sent in the request.
+     */
+
+    public static final int SC_NOT_ACCEPTABLE = 406;
+
+    /**
+     * Status code (407) indicating that the client <em>MUST</em> first
+     * authenticate itself with the proxy.
+     */
+
+    public static final int SC_PROXY_AUTHENTICATION_REQUIRED = 407;
+
+    /**
+     * Status code (408) indicating that the client did not produce a
+     * request within the time that the server was prepared to wait.
+     */
+
+    public static final int SC_REQUEST_TIMEOUT = 408;
+
+    /**
+     * Status code (409) indicating that the request could not be
+     * completed due to a conflict with the current state of the
+     * resource.
+     */
+
+    public static final int SC_CONFLICT = 409;
+
+    /**
+     * Status code (410) indicating that the resource is no longer
+     * available at the server and no forwarding address is known.
+     * This condition <em>SHOULD</em> be considered permanent.
+     */
+
+    public static final int SC_GONE = 410;
+
+    /**
+     * Status code (411) indicating that the request cannot be handled
+     * without a defined <code><em>Content-Length</em></code>.
+     */
+
+    public static final int SC_LENGTH_REQUIRED = 411;
+
+    /**
+     * Status code (412) indicating that the precondition given in one
+     * or more of the request-header fields evaluated to false when it
+     * was tested on the server.
+     */
+
+    public static final int SC_PRECONDITION_FAILED = 412;
+
+    /**
+     * Status code (413) indicating that the server is refusing to process
+     * the request because the request entity is larger than the server is
+     * willing or able to process.
+     */
+
+    public static final int SC_REQUEST_ENTITY_TOO_LARGE = 413;
+
+    /**
+     * Status code (414) indicating that the server is refusing to service
+     * the request because the <code><em>Request-URI</em></code> is longer
+     * than the server is willing to interpret.
+     */
+
+    public static final int SC_REQUEST_URI_TOO_LONG = 414;
+
+    /**
+     * Status code (415) indicating that the server is refusing to service
+     * the request because the entity of the request is in a format not
+     * supported by the requested resource for the requested method.
+     */
+
+    public static final int SC_UNSUPPORTED_MEDIA_TYPE = 415;
+
+    /**
+     * Status code (416) indicating that the server cannot serve the
+     * requested byte range.
+     */
+
+    public static final int SC_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
+
+    /**
+     * Status code (417) indicating that the server could not meet the
+     * expectation given in the Expect request header.
+     */
+
+    public static final int SC_EXPECTATION_FAILED = 417;
+
+    /**
+     * Status code (423) indicating the destination resource of a
+     * method is locked, and either the request did not contain a
+     * valid Lock-Info header, or the Lock-Info header identifies
+     * a lock held by another principal.
+     */
+    public static final int SC_LOCKED = 423;
+
+    /**
+     * Status code (500) indicating an error inside the HTTP server
+     * which prevented it from fulfilling the request.
+     */
+
+    public static final int SC_INTERNAL_SERVER_ERROR = 500;
+
+    /**
+     * Status code (501) indicating the HTTP server does not support
+     * the functionality needed to fulfill the request.
+     */
+
+    public static final int SC_NOT_IMPLEMENTED = 501;
+
+    /**
+     * Status code (502) indicating that the HTTP server received an
+     * invalid response from a server it consulted when acting as a
+     * proxy or gateway.
+     */
+
+    public static final int SC_BAD_GATEWAY = 502;
+
+    /**
+     * Status code (503) indicating that the HTTP server is
+     * temporarily overloaded, and unable to handle the request.
+     */
+
+    public static final int SC_SERVICE_UNAVAILABLE = 503;
+
+    /**
+     * Status code (504) indicating that the server did not receive
+     * a timely response from the upstream server while acting as
+     * a gateway or proxy.
+     */
+
+    public static final int SC_GATEWAY_TIMEOUT = 504;
+
+    /**
+     * Status code (505) indicating that the server does not support
+     * or refuses to support the HTTP protocol version that was used
+     * in the request message.
+     */
+
+    public static final int SC_HTTP_VERSION_NOT_SUPPORTED = 505;
+
+    // will not be recycled
+    public Object nativeResponse;
+
+    protected CBuffer message = CBuffer.newInstance();
+
+    int status = -1;
+
+    HttpResponse(HttpChannel httpCh) {
+        super(httpCh);
+    }
+
+    public void recycle() {
+        super.recycle();
+        message.recycle();
+        status = -1;
+    }
+
+    public void setMessage(String s) {
+        message.set(filter(s));
+    }
+
+    public String getMessage() {
+        return message.toString();
+    }
+
+    public CBuffer getMessageBuffer() {
+        return message;
+    }
+
+    byte[] S_200 = new byte[] { '2', '0', '0' };
+
+    public void setStatus(int i) {
+        status = i;
+    }
+
+    public void sendError(int status) {
+        this.status = status;
+    }
+
+    public void sendError(int status, String msg) {
+        message.set(msg);
+    }
+
+    public int getStatus() {
+        if (status >= 0) {
+            return status;
+        }
+        if (getMsgBytes().status().isNull()) {
+            status = 200;
+        } else {
+            try {
+                status = getMsgBytes().status().getInt();
+            } catch(NumberFormatException ex) {
+                status = 500;
+                httpCh.log.severe("Invalid status " + getMsgBytes().status());
+            }
+        }
+        return status;
+    }
+
+    public HttpRequest getRequest() {
+        return getHttpChannel().getRequest();
+    }
+
+    // Http client mode.
+    protected void processReceivedHeaders() throws IOException {
+        protocol().set(getMsgBytes().protocol());
+        message.set(getMsgBytes().message());
+        processMimeHeaders();
+        // TODO: if protocol == 1.0 and we requested 1.1, downgrade getHttpChannel().pro
+        try {
+            status = getStatus();
+        } catch (Throwable t) {
+            getHttpChannel().log.warning("Invalid status " + getMsgBytes().status() + " " + getMessage());
+        }
+    }
+
+    /**
+     * All responses to the HEAD request method MUST NOT include a
+     * message-body, even though the presence of entity- header fields might
+     *  lead one to believe they do. All 1xx (informational), 204 (no content)
+     *  , and 304 (not modified) responses MUST NOT include a message-body. All
+     *  other responses do include a message-body, although it MAY be of zero
+     *  length.
+     */
+    public boolean hasBody() {
+        if (httpCh.getRequest().method().equals("HEAD")) {
+            return false;
+        }
+        if (status >= 100 && status < 200) {
+            return false;
+        }
+        // what about (status == 205) ?
+        if ((status == 204)
+                || (status == 304)) {
+            return false;
+        }
+        return true;
+    }
+
+    /** Get the status string associated with a status code.
+     *  No I18N - return the messages defined in the HTTP spec.
+     *  ( the user isn't supposed to see them, this is the last
+     *  thing to translate)
+     *
+     *  Common messages are cached.
+     *
+     */
+    static BBucket getMessage( int status ) {
+        // method from Response.
+
+        // Does HTTP requires/allow international messages or
+        // are pre-defined? The user doesn't see them most of the time
+        switch( status ) {
+        case 200:
+            return st_200;
+        case 302:
+            return st_302;
+        case 400:
+            return st_400;
+        case 404:
+            return st_404;
+        }
+        BBucket bb = stats.get(status);
+        if (bb == null) {
+            return st_unknown;
+        }
+        return bb;
+    }
+
+    public static String getStatusText(int code) {
+        return getMessage(code).toString();
+    }
+
+    static BBucket st_unknown = BBuffer.wrapper("No Message");
+    static BBucket st_200 = BBuffer.wrapper("OK");
+    static BBucket st_302= BBuffer.wrapper("Moved Temporarily");
+    static BBucket st_400= BBuffer.wrapper("Bad Request");
+    static BBucket st_404= BBuffer.wrapper("Not Found");
+
+    static HashMap<Integer,BBucket> stats = new HashMap<Integer, BBucket>();
+    private static void addStatus(int stat, String msg) {
+        stats.put(stat, BBuffer.wrapper(msg));
+    }
+
+    static {
+        addStatus(100, "Continue");
+        addStatus(101, "Switching Protocols");
+        addStatus(200, "OK");
+        addStatus(201, "Created");
+        addStatus(202, "Accepted");
+        addStatus(203, "Non-Authoritative Information");
+        addStatus(204, "No Content");
+        addStatus(205, "Reset Content");
+        addStatus(206, "Partial Content");
+        addStatus(207, "Multi-Status");
+        addStatus(300, "Multiple Choices");
+        addStatus(301, "Moved Permanently");
+        addStatus(302, "Moved Temporarily");
+        addStatus(303, "See Other");
+        addStatus(304, "Not Modified");
+        addStatus(305, "Use Proxy");
+        addStatus(307, "Temporary Redirect");
+        addStatus(400, "Bad Request");
+        addStatus(401, "Unauthorized");
+        addStatus(402, "Payment Required");
+        addStatus(403, "Forbidden");
+        addStatus(404, "Not Found");
+        addStatus(405, "Method Not Allowed");
+        addStatus(406, "Not Acceptable");
+        addStatus(407, "Proxy Authentication Required");
+        addStatus(408, "Request Timeout");
+        addStatus(409, "Conflict");
+        addStatus(410, "Gone");
+        addStatus(411, "Length Required");
+        addStatus(412, "Precondition Failed");
+        addStatus(413, "Request Entity Too Large");
+        addStatus(414, "Request-URI Too Long");
+        addStatus(415, "Unsupported Media Type");
+        addStatus(416, "Requested Range Not Satisfiable");
+        addStatus(417, "Expectation Failed");
+        addStatus(422, "Unprocessable Entity");
+        addStatus(423, "Locked");
+        addStatus(424, "Failed Dependency");
+        addStatus(500, "Internal Server Error");
+        addStatus(501, "Not Implemented");
+        addStatus(502, "Bad Gateway");
+        addStatus(503, "Service Unavailable");
+        addStatus(504, "Gateway Timeout");
+        addStatus(505, "HTTP Version Not Supported");
+        addStatus(507, "Insufficient Storage");
+        addStatus(SC_LOCKED, "Locked");
+
+
+    }
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.  This avoids potential attacks caused by including JavaScript
+     * codes in the request URL that is often reported in error messages.
+     *
+     * @param message The message string to be filtered
+     */
+    private static String filter(String message) {
+
+        if (message == null)
+            return (null);
+        if (message.indexOf('<') < 0 &&
+                message.indexOf('>') < 0 &&
+                message.indexOf('&') < 0 &&
+                message.indexOf('"') < 0) {
+            return message;
+        }
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+
+        StringBuffer result = new StringBuffer(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpServer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpServer.java
new file mode 100644
index 0000000..799e4c4
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpServer.java
@@ -0,0 +1,35 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import org.apache.tomcat.lite.io.SocketConnector;
+import org.apache.tomcat.lite.io.SslProvider;
+import org.apache.tomcat.lite.io.jsse.JsseSslProvider;
+
+/**
+ * Main entry point for HTTP server code.
+ *
+ * ( initial draft - will replace statics, add helpers, etc )
+ */
+public class HttpServer {
+    static SslProvider sslConC = new JsseSslProvider();
+
+    public synchronized static HttpConnector newServer(int port) {
+        return new HttpConnector(new SocketConnector()).
+            withSsl(sslConC).setPort(port);
+    }
+
+    public synchronized static HttpConnector newSslServer(int port) {
+        // DHE broken in harmony - will replace with a flag
+        //      SslConnector.setEnabledCiphers(new String[] {
+        //              "TLS_RSA_WITH_3DES_EDE_CBC_SHA"
+        //      });
+        // -cipher DES-CBC3-SHA
+
+        SslProvider sslCon = new JsseSslProvider();
+
+        return new HttpConnector(new SocketConnector()).
+            withSsl(sslCon).setPort(port).setServerSsl(true);
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java
new file mode 100644
index 0000000..b18e0ac
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/HttpWriter.java
@@ -0,0 +1,313 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.tomcat.lite.io.IOOutputStream;
+import org.apache.tomcat.lite.io.IOWriter;
+
+/**
+ * Implement character translation and buffering.
+ *
+ * The actual buffering happens in the IOBuffer - we translate the
+ * chars as soon as we get them.
+ *
+ * For servlet compat you can set a buffer size and a flush() will happen
+ * when the number of chars have been written. Note that writes at a lower
+ * layer can be done and are not counted.
+ *
+ * @author Costin Manolache
+ */
+public class HttpWriter extends Writer {
+
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+    public static final int DEFAULT_BUFFER_SIZE = 8*1024;
+
+    // ----------------------------------------------------- Instance Variables
+    HttpMessage message;
+
+    /**
+     * The byte buffer.
+     */
+    protected IOOutputStream bb;
+
+    int bufferSize = DEFAULT_BUFFER_SIZE;
+
+    /**
+     * Number of chars written.
+     */
+    protected int wSinceFlush = 0;
+
+
+    /**
+     * Flag which indicates if the output buffer is closed.
+     */
+    protected boolean closed = false;
+
+    /**
+     * Encoding to use.
+     * TODO: isn't it redundant ? enc, gotEnc, conv plus the enc in the bb
+     */
+    protected String enc;
+
+
+    /**
+     * Encoder is set.
+     */
+    protected boolean gotEnc = false;
+
+
+    /**
+     * List of encoders. The writer is reused - the encoder mapping
+     * avoids creating expensive objects. In future it'll contain nio.Charsets
+     */
+    //protected Map<String, C2BConverter> encoders = new HashMap();
+
+
+    /**
+     * Current char to byte converter. TODO: replace with Charset
+     */
+    private IOWriter conv;
+
+    /**
+     * Suspended flag. All output bytes will be swallowed if this is true.
+     */
+    protected boolean suspended = false;
+
+
+    // ----------------------------------------------------------- Constructors
+
+
+    /**
+     * Default constructor. Allocate the buffer with the default buffer size.
+     * @param out
+     */
+    public HttpWriter(HttpMessage message, IOOutputStream out,
+            IOWriter conv) {
+        this.message = message;
+        bb = out;
+        this.conv = conv;
+    }
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Is the response output suspended ?
+     *
+     * @return suspended flag value
+     */
+    public boolean isSuspended() {
+        return this.suspended;
+    }
+
+
+    /**
+     * Set the suspended flag.
+     *
+     * @param suspended New suspended flag value
+     */
+    public void setSuspended(boolean suspended) {
+        this.suspended = suspended;
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Recycle the output buffer.
+     */
+    public void recycle() {
+        wSinceFlush = 0;
+        bb.recycle();
+        closed = false;
+        suspended = false;
+
+//        if (conv != null) {
+//            conv.recycle();
+//        }
+
+        gotEnc = false;
+        enc = null;
+    }
+
+    public void close()
+        throws IOException {
+
+        if (closed)
+            return;
+        if (suspended)
+            return;
+
+        push();
+        closed = true;
+
+        bb.close();
+    }
+
+
+    /**
+     * Flush bytes or chars contained in the buffer.
+     *
+     * @throws IOException An underlying IOException occurred
+     */
+    public void flush()
+            throws IOException {
+        push();
+        bb.flush(); // will send the data
+        wSinceFlush = 0;
+    }
+
+    /**
+     * Flush chars to the byte buffer.
+     */
+    public void push()
+        throws IOException {
+
+        if (suspended)
+            return;
+        getConv().push();
+
+    }
+
+
+    private void updateSize(int cnt) throws IOException {
+        wSinceFlush += cnt;
+        if (wSinceFlush > bufferSize) {
+            flush();
+        }
+    }
+
+    public void write(int c)
+            throws IOException {
+        if (suspended)
+            return;
+        getConv().write(c);
+        updateSize(1);
+    }
+
+
+    public void write(char c[])
+            throws IOException {
+        write(c, 0, c.length);
+    }
+
+
+    public void write(char c[], int off, int len)
+            throws IOException {
+        if (suspended)
+            return;
+        getConv().write(c, off, len);
+        updateSize(len);
+    }
+
+
+    public void write(StringBuffer sb)
+            throws IOException {
+        if (suspended)
+            return;
+        int len = sb.length();
+        getConv().write(sb.toString());
+        updateSize(len);
+    }
+
+
+    /**
+     * Append a string to the buffer
+     */
+    public void write(String s, int off, int len)
+        throws IOException {
+        if (suspended)
+            return;
+        if (s==null)
+            s="null";
+        getConv().write( s, off, len );
+        updateSize(len);
+    }
+
+
+    public void write(String s)
+            throws IOException {
+        if (s==null)
+            s="null";
+        write(s, 0, s.length());
+    }
+
+    public void println() throws IOException {
+        write("\n");
+    }
+
+    public void println(String s) throws IOException {
+        write(s);
+        write("\n");
+    }
+
+    public void print(String s) throws IOException {
+        write(s);
+    }
+
+    public void checkConverter()
+            throws IOException {
+//        if (gotEnc) {
+//            return;
+//        }
+//        if (enc == null) {
+//            enc = message.getCharacterEncoding();
+//        }
+//
+//        gotEnc = true;
+//        if (enc == null)
+//            enc = DEFAULT_ENCODING;
+//        conv = (C2BConverter) encoders.get(enc);
+//
+//        if (conv == null) {
+//            conv = C2BConverter.newConverter(message.getBodyOutputStream(),
+//                    enc);
+//            encoders.put(enc, conv);
+//
+//        }
+    }
+
+    public int getWrittenSinceFlush() {
+        return wSinceFlush;
+    }
+
+
+    public void setBufferSize(int size) {
+        if (size > bufferSize) {
+            bufferSize = size;
+        }
+    }
+
+    /**
+     *  Clear any data that was buffered.
+     */
+    public void reset() {
+        if (conv != null) {
+            conv.recycle();
+        }
+        wSinceFlush = 0;
+        gotEnc = false;
+        enc = null;
+        bb.reset();
+    }
+
+
+    public int getBufferSize() {
+        return bufferSize;
+    }
+
+    protected IOWriter getConv() throws IOException {
+        checkConverter();
+        return conv;
+    }
+
+    public void println(CharSequence key) throws IOException {
+        // TODO: direct
+        println(key.toString());
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java
new file mode 100644
index 0000000..1b8d939
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MappingData.java
@@ -0,0 +1,69 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+import org.apache.tomcat.lite.io.CBuffer;
+
+
+/**
+ * Mapping data.
+ *
+ * @author Remy Maucherat
+ */
+public class MappingData {
+
+    public Object context = null; // ServletContextImpl
+
+    public BaseMapper.Context contextMap;
+
+    public BaseMapper.ServiceMapping service = null;
+
+    public CBuffer contextPath = CBuffer.newInstance();
+    public CBuffer requestPath = CBuffer.newInstance();
+    public CBuffer wrapperPath = CBuffer.newInstance();
+    public CBuffer pathInfo = CBuffer.newInstance();
+
+    public CBuffer redirectPath = CBuffer.newInstance();
+
+    // Extension
+    CBuffer ext = CBuffer.newInstance();
+    CBuffer tmpPrefix = CBuffer.newInstance();
+
+    // Excluding context path, with a '/' added if needed
+    CBuffer tmpServletPath = CBuffer.newInstance();
+
+    // Excluding context path, with a '/' added if needed
+    CBuffer tmpWelcome = CBuffer.newInstance();
+
+    public void recycle() {
+        service = null;
+        context = null;
+        pathInfo.recycle();
+        requestPath.recycle();
+        wrapperPath.recycle();
+        contextPath.recycle();
+        redirectPath.recycle();
+        contextMap = null;
+    }
+
+
+    public Object getServiceObject() {
+        return service == null ? null : service.object;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java
new file mode 100644
index 0000000..7b8fb14
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/MultiMap.java
@@ -0,0 +1,357 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBucket;
+import org.apache.tomcat.lite.io.CBuffer;
+
+/**
+ * Map used to represent headers and parameters ( could be used
+ * for cookies too )
+ *
+ * It'll avoid garbage collection, like original tomcat classes,
+ * by converting to chars and strings late.
+ *
+ * Not thread safe.
+ */
+public class MultiMap {
+
+    public static class Entry {
+        // Wrappers from the head message bytes.
+        BBuffer nameB;
+        BBuffer valueB;
+
+        CBuffer key = CBuffer.newInstance();
+        private CBuffer value = CBuffer.newInstance();
+
+        /**
+         * For the first entry with a given name: list of all
+         * other entries, including this one, with same name.
+         *
+         * For second or more: empty list
+         */
+        public List<Entry> values = new ArrayList<Entry>();
+
+        public void recycle() {
+            key.recycle();
+            value.recycle();
+            //next=null;
+            nameB = null;
+            valueB = null;
+            values.clear();
+        }
+
+        public CBuffer getName() {
+            if (key.length() == 0 && nameB != null) {
+                key.set(nameB);
+            }
+            return key;
+        }
+
+        public CBuffer getValue() {
+            if (value.length() == 0 && valueB != null) {
+                value.set(valueB);
+            }
+            return value;
+        }
+
+        /** Important - used by values iterator, returns strings
+         * from each entry
+         */
+        public String toString() {
+            return getValue().toString();
+        }
+
+    }
+
+    // active entries
+    protected int count;
+
+    // The key will be converted to lower case
+    boolean toLower = false;
+
+    // Some may be inactive - up to count.
+    protected List<Entry> entries = new ArrayList<Entry>();
+
+    // 2 options: convert all header/param names to String
+    // or use a temp CBuffer to map
+    Map<CBuffer, Entry> map =
+        new HashMap<CBuffer, Entry>();
+
+    public void recycle() {
+        for (int i = 0; i < count; i++) {
+            Entry entry = entries.get(i);
+            entry.recycle();
+        }
+        count = 0;
+        map.clear();
+    }
+
+    // ----------- Mutations ------------------------
+
+    protected Entry newEntry()  {
+        return new Entry();
+    }
+
+    /**
+     * Adds a partially constructed field entry.
+     * Updates count - but will not affect the map.
+     */
+    private Entry getEntryForAdd() {
+        Entry entry;
+        if (count >= entries.size()) {
+            entry = newEntry();
+            entries.add(entry);
+        } else {
+            entry = entries.get(count);
+        }
+        count++;
+        return entry;
+    }
+
+
+    /** Create a new named header , return the CBuffer
+     *  container for the new value
+     */
+   public Entry addEntry(CharSequence name ) {
+       Entry mh = getEntryForAdd();
+       mh.getName().append(name);
+       if (toLower) {
+           mh.getName().toLower();
+       }
+       updateMap(mh);
+       return mh;
+   }
+
+   /** Create a new named header , return the CBuffer
+    *  container for the new value
+    */
+   public Entry addEntry(BBuffer name ) {
+       Entry mh = getEntryForAdd();
+       mh.nameB = name;
+       if (toLower) {
+           mh.getName().toLower();
+       }
+       updateMap(mh);
+
+       return mh;
+   }
+
+   private void updateMap(Entry mh) {
+       Entry topEntry = map.get(mh.getName());
+
+       if (topEntry == null) {
+           map.put(mh.getName(), mh);
+           mh.values.add(mh);
+       } else {
+           topEntry.values.add(mh);
+       }
+   }
+
+
+
+    public void remove(CharSequence key) {
+        CBucket ckey = key(key);
+        Entry entry = getEntry(ckey);
+        if (entry != null) {
+            map.remove(ckey);
+
+            for (int i = count - 1; i >= 0; i--) {
+                entry = entries.get(i);
+                if (entry.getName().equals(key)) {
+                    entry.recycle();
+                    entries.remove(i);
+                    count--;
+                }
+            }
+        }
+    }
+
+    // --------------- Key-based access --------------
+    CBuffer tmpKey = CBuffer.newInstance();
+
+    /**
+     * Finds and returns a header field with the given name.  If no such
+     * field exists, null is returned.  If more than one such field is
+     * in the header, an arbitrary one is returned.
+     */
+    public CBuffer getHeader(String name) {
+        for (int i = 0; i < count; i++) {
+            if (entries.get(i).getName().equalsIgnoreCase(name)) {
+                return entries.get(i).getValue();
+            }
+        }
+        return null;
+    }
+
+    private CBucket key(CharSequence key) {
+        if (key instanceof CBucket) {
+            CBucket res = (CBucket) key;
+            if (!toLower || !res.hasUpper()) {
+                return res;
+            }
+        }
+        tmpKey.recycle();
+        tmpKey.append(key);
+        if (toLower) {
+            tmpKey.toLower();
+        }
+        return tmpKey;
+    }
+
+    public Entry getEntry(CharSequence key) {
+        Entry entry = map.get(key(key));
+        return entry;
+    }
+
+    public Entry getEntry(CBucket buf) {
+        // lowercase ?
+        Entry entry = map.get(buf);
+        return entry;
+    }
+
+    public Enumeration<String> names() {
+        return new IteratorEnumerator(map.keySet().iterator());
+    }
+
+    // ----------- Index access --------------
+
+    /**
+     *  Number of entries ( including those with same key
+     *
+     * @return
+     */
+    public int size() {
+        return count;
+    }
+
+
+    public CharSequence getKey(int idx) {
+        return entries.get(idx).key;
+    }
+
+    public Entry getEntry(int idx) {
+        return entries.get(idx);
+    }
+
+    /**
+     * Returns the Nth header name, or null if there is no such header.
+     * This may be used to iterate through all header fields.
+     */
+    public CBuffer getName(int n) {
+        return n < count ? entries.get(n).getName() : null;
+    }
+
+    /**
+     * Returns the Nth header value, or null if there is no such header.
+     * This may be used to iterate through all header fields.
+     */
+    public CBuffer getValue(int n) {
+        return n >= 0 && n < count ? entries.get(n).getValue() : null;
+    }
+
+    // ----------- Helpers --------------
+    public void add(CharSequence key, CharSequence value) {
+        Entry mh = addEntry(key);
+        mh.value.append(value);
+    }
+
+    /** Create a new named header , return the CBuffer
+     * container for the new value
+     */
+    public CBuffer addValue( String name ) {
+        return addEntry(name).getValue();
+    }
+
+     public Entry setEntry( String name ) {
+         remove(name);
+         return addEntry(name);
+     }
+
+     public void set(CharSequence key, CharSequence value) {
+         remove(key);
+         add(key, value);
+     }
+
+     public CBuffer setValue( String name ) {
+         remove(name);
+         return addValue(name);
+     }
+
+     public CBuffer get(CharSequence key) {
+         Entry entry = getEntry(key);
+         return (entry == null) ? null : entry.value;
+     }
+
+     public String getString(CharSequence key) {
+         Entry entry = getEntry(key);
+         return (entry == null) ? null : entry.value.toString();
+     }
+
+
+    // -------------- support classes ----------------
+
+    public static class IteratorEnumerator implements Enumeration<String> {
+        private final Iterator keyI;
+
+        public IteratorEnumerator(Iterator iterator) {
+            this.keyI = iterator;
+        }
+
+
+        public boolean hasMoreElements() {
+            return keyI.hasNext();
+        }
+
+
+        public String nextElement() {
+            return keyI.next().toString();
+        }
+
+    }
+
+    public static final Enumeration<String> EMPTY =
+        new Enumeration<String>() {
+
+            @Override
+            public boolean hasMoreElements() {
+                return false;
+            }
+
+            @Override
+            public String nextElement() {
+                return null;
+            }
+
+    };
+
+    public MultiMap insensitive() {
+        toLower = true;
+        return this;
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ServerCookie.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ServerCookie.java
new file mode 100644
index 0000000..4cf66f2
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/ServerCookie.java
@@ -0,0 +1,819 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http;
+
+import java.io.Serializable;
+import java.text.DateFormat;
+import java.text.FieldPosition;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.List;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+
+
+/**
+ *  Server-side cookie representation.
+ *  Allows recycling and uses MessageBytes as low-level
+ *  representation ( and thus the byte-> char conversion can be delayed
+ *  until we know the charset ).
+ *
+ *  Tomcat.core uses this recyclable object to represent cookies,
+ *  and the facade will convert it to the external representation.
+ */
+public class ServerCookie implements Serializable {
+
+    // Version 0 (Netscape) attributes
+    private BBuffer name = BBuffer.allocate();
+    private BBuffer value = BBuffer.allocate();
+
+    private CBuffer nameC = CBuffer.newInstance();
+
+    // Expires - Not stored explicitly. Generated from Max-Age (see V1)
+    private BBuffer path = BBuffer.allocate();
+    private BBuffer domain = BBuffer.allocate();
+    private boolean secure;
+
+    // Version 1 (RFC2109) attributes
+    private BBuffer comment = BBuffer.allocate();
+    private int maxAge = -1;
+    private int version = 0;
+
+    // Other fields
+    private static final String OLD_COOKIE_PATTERN =
+        "EEE, dd-MMM-yyyy HH:mm:ss z";
+    private static final ThreadLocal<DateFormat> OLD_COOKIE_FORMAT =
+        new ThreadLocal<DateFormat>() {
+        protected DateFormat initialValue() {
+            DateFormat df =
+                new SimpleDateFormat(OLD_COOKIE_PATTERN, Locale.US);
+            df.setTimeZone(TimeZone.getTimeZone("GMT"));
+            return df;
+        }
+    };
+
+    private static final String ancientDate;
+
+
+    static {
+        ancientDate = OLD_COOKIE_FORMAT.get().format(new Date(10000));
+    }
+
+    /**
+     * If set to true, we parse cookies according to the servlet spec,
+     */
+    public static final boolean STRICT_SERVLET_COMPLIANCE =
+        Boolean.valueOf(System.getProperty("org.apache.catalina.STRICT_SERVLET_COMPLIANCE", "false")).booleanValue();
+
+    /**
+     * If set to false, we don't use the IE6/7 Max-Age/Expires work around
+     */
+    public static final boolean ALWAYS_ADD_EXPIRES =
+        Boolean.valueOf(System.getProperty("org.apache.tomcat.util.http.ServerCookie.ALWAYS_ADD_EXPIRES", "true")).booleanValue();
+
+    // Note: Servlet Spec =< 2.5 only refers to Netscape and RFC2109,
+    // not RFC2965
+
+    // Version 1 (RFC2965) attributes
+    // TODO Add support for CommentURL
+    // Discard - implied by maxAge <0
+    // TODO Add support for Port
+
+    public ServerCookie() {
+    }
+
+    public void recycle() {
+        path.recycle();
+        name.recycle();
+        value.recycle();
+        comment.recycle();
+        maxAge=-1;
+        path.recycle();
+        domain.recycle();
+        version=0;
+        secure=false;
+    }
+
+    public BBuffer getComment() {
+        return comment;
+    }
+
+    public BBuffer getDomain() {
+        return domain;
+    }
+
+    public void setMaxAge(int expiry) {
+        maxAge = expiry;
+    }
+
+    public int getMaxAge() {
+        return maxAge;
+    }
+
+    public BBuffer getPath() {
+        return path;
+    }
+
+    public void setSecure(boolean flag) {
+        secure = flag;
+    }
+
+    public boolean getSecure() {
+        return secure;
+    }
+
+    public BBuffer getName() {
+        return name;
+    }
+
+    public BBuffer getValue() {
+        return value;
+    }
+
+    public int getVersion() {
+        return version;
+    }
+
+    public void setVersion(int v) {
+        version = v;
+    }
+
+
+    // -------------------- utils --------------------
+
+    public String toString() {
+        return "Cookie " + getName() + "=" + getValue() + " ; "
+            + getVersion() + " " + getPath() + " " + getDomain();
+    }
+
+    private static final String tspecials = ",; ";
+    private static final String tspecials2 = "()<>@,;:\\\"/[]?={} \t";
+    private static final String tspecials2NoSlash = "()<>@,;:\\\"[]?={} \t";
+
+    /*
+     * Tests a string and returns true if the string counts as a
+     * reserved token in the Java language.
+     *
+     * @param value the <code>String</code> to be tested
+     *
+     * @return      <code>true</code> if the <code>String</code> is a reserved
+     *              token; <code>false</code> if it is not
+     */
+    public static boolean isToken(String value) {
+        return isToken(value,null);
+    }
+
+    public static boolean isToken(String value, String literals) {
+        String tspecials = (literals==null?ServerCookie.tspecials:literals);
+        if( value==null) return true;
+        int len = value.length();
+
+        for (int i = 0; i < len; i++) {
+            char c = value.charAt(i);
+
+            if (tspecials.indexOf(c) != -1)
+                return false;
+        }
+        return true;
+    }
+
+    public static boolean containsCTL(String value, int version) {
+        if( value==null) return false;
+        int len = value.length();
+        for (int i = 0; i < len; i++) {
+            char c = value.charAt(i);
+            if (c < 0x20 || c >= 0x7f) {
+                if (c == 0x09)
+                    continue; //allow horizontal tabs
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static boolean isToken2(String value) {
+        return isToken2(value,null);
+    }
+
+    public static boolean isToken2(String value, String literals) {
+        String tspecials2 = (literals==null?ServerCookie.tspecials2:literals);
+        if( value==null) return true;
+        int len = value.length();
+
+        for (int i = 0; i < len; i++) {
+            char c = value.charAt(i);
+            if (tspecials2.indexOf(c) != -1)
+                return false;
+        }
+        return true;
+    }
+
+    // -------------------- Cookie parsing tools
+
+
+    /**
+     * Return the header name to set the cookie, based on cookie version.
+     */
+    public String getCookieHeaderName() {
+        return getCookieHeaderName(version);
+    }
+
+    /**
+     * Return the header name to set the cookie, based on cookie version.
+     */
+    public static String getCookieHeaderName(int version) {
+        // TODO Re-enable logging when RFC2965 is implemented
+        // log( (version==1) ? "Set-Cookie2" : "Set-Cookie");
+        if (version == 1) {
+            // XXX RFC2965 not referenced in Servlet Spec
+            // Set-Cookie2 is not supported by Netscape 4, 6, IE 3, 5
+            // Set-Cookie2 is supported by Lynx and Opera
+            // Need to check on later IE and FF releases but for now...
+            // RFC2109
+            return "Set-Cookie";
+            // return "Set-Cookie2";
+        } else {
+            // Old Netscape
+            return "Set-Cookie";
+        }
+    }
+
+    // TODO RFC2965 fields also need to be passed
+    public static void appendCookieValue( StringBuffer headerBuf,
+                                          int version,
+                                          String name,
+                                          String value,
+                                          String path,
+                                          String domain,
+                                          String comment,
+                                          int maxAge,
+                                          boolean isSecure,
+                                          boolean isHttpOnly)
+    {
+        StringBuffer buf = new StringBuffer();
+        // Servlet implementation checks name
+        buf.append( name );
+        buf.append("=");
+        // Servlet implementation does not check anything else
+
+        version = maybeQuote2(version, buf, value,true);
+
+        // Add version 1 specific information
+        if (version == 1) {
+            // Version=1 ... required
+            buf.append ("; Version=1");
+
+            // Comment=comment
+            if ( comment!=null ) {
+                buf.append ("; Comment=");
+                maybeQuote2(version, buf, comment);
+            }
+        }
+
+        // Add domain information, if present
+        if (domain!=null) {
+            buf.append("; Domain=");
+            maybeQuote2(version, buf, domain);
+        }
+
+        // Max-Age=secs ... or use old "Expires" format
+        // TODO RFC2965 Discard
+        if (maxAge >= 0) {
+            if (version > 0) {
+                buf.append ("; Max-Age=");
+                buf.append (maxAge);
+            }
+            // IE6, IE7 and possibly other browsers don't understand Max-Age.
+            // They do understand Expires, even with V1 cookies!
+            if (version == 0 || ALWAYS_ADD_EXPIRES) {
+                // Wdy, DD-Mon-YY HH:MM:SS GMT ( Expires Netscape format )
+                buf.append ("; Expires=");
+                // To expire immediately we need to set the time in past
+                if (maxAge == 0)
+                    buf.append( ancientDate );
+                else
+                    OLD_COOKIE_FORMAT.get().format(
+                            new Date(System.currentTimeMillis() +
+                                    maxAge*1000L),
+                            buf, new FieldPosition(0));
+            }
+        }
+
+        // Path=path
+        if (path!=null) {
+            buf.append ("; Path=");
+            if (version==0) {
+                maybeQuote2(version, buf, path);
+            } else {
+                maybeQuote2(version, buf, path, ServerCookie.tspecials2NoSlash, false);
+            }
+        }
+
+        // Secure
+        if (isSecure) {
+          buf.append ("; Secure");
+        }
+
+        // HttpOnly
+        if (isHttpOnly) {
+            buf.append("; HttpOnly");
+        }
+        headerBuf.append(buf);
+    }
+
+    public static boolean alreadyQuoted (String value) {
+        if (value==null || value.length()==0) return false;
+        return (value.charAt(0)=='\"' && value.charAt(value.length()-1)=='\"');
+    }
+
+    /**
+     * Quotes values using rules that vary depending on Cookie version.
+     * @param version
+     * @param buf
+     * @param value
+     */
+    public static int maybeQuote2 (int version, StringBuffer buf, String value) {
+        return maybeQuote2(version,buf,value,false);
+    }
+
+    public static int maybeQuote2 (int version, StringBuffer buf, String value, boolean allowVersionSwitch) {
+        return maybeQuote2(version,buf,value,null,allowVersionSwitch);
+    }
+
+    public static int maybeQuote2 (int version, StringBuffer buf, String value, String literals, boolean allowVersionSwitch) {
+        if (value==null || value.length()==0) {
+            buf.append("\"\"");
+        }else if (containsCTL(value,version))
+            throw new IllegalArgumentException("Control character in cookie value, consider BASE64 encoding your value");
+        else if (alreadyQuoted(value)) {
+            buf.append('"');
+            buf.append(escapeDoubleQuotes(value,1,value.length()-1));
+            buf.append('"');
+        } else if (allowVersionSwitch && (!STRICT_SERVLET_COMPLIANCE) && version==0 && !isToken2(value, literals)) {
+            buf.append('"');
+            buf.append(escapeDoubleQuotes(value,0,value.length()));
+            buf.append('"');
+            version = 1;
+        } else if (version==0 && !isToken(value,literals)) {
+            buf.append('"');
+            buf.append(escapeDoubleQuotes(value,0,value.length()));
+            buf.append('"');
+        } else if (version==1 && !isToken2(value,literals)) {
+            buf.append('"');
+            buf.append(escapeDoubleQuotes(value,0,value.length()));
+            buf.append('"');
+        }else {
+            buf.append(value);
+        }
+        return version;
+    }
+
+
+    /**
+     * Escapes any double quotes in the given string.
+     *
+     * @param s the input string
+     * @param beginIndex start index inclusive
+     * @param endIndex exclusive
+     * @return The (possibly) escaped string
+     */
+    private static String escapeDoubleQuotes(String s, int beginIndex, int endIndex) {
+
+        if (s == null || s.length() == 0 || s.indexOf('"') == -1) {
+            return s;
+        }
+
+        StringBuffer b = new StringBuffer();
+        for (int i = beginIndex; i < endIndex; i++) {
+            char c = s.charAt(i);
+            if (c == '\\' ) {
+                b.append(c);
+                //ignore the character after an escape, just append it
+                if (++i>=endIndex) throw new IllegalArgumentException("Invalid escape character in cookie value.");
+                b.append(s.charAt(i));
+            } else if (c == '"')
+                b.append('\\').append('"');
+            else
+                b.append(c);
+        }
+
+        return b.toString();
+    }
+
+    /**
+     * Unescapes any double quotes in the given cookie value.
+     *
+     * @param bc The cookie value to modify
+     */
+    public static void unescapeDoubleQuotes(BBuffer bc) {
+
+        if (bc == null || bc.getLength() == 0 || bc.indexOf('"', 0) == -1) {
+            return;
+        }
+
+        int src = bc.getStart();
+        int end = bc.getEnd();
+        int dest = src;
+        byte[] buffer = bc.array();
+
+        while (src < end) {
+            if (buffer[src] == '\\' && src < end && buffer[src+1]  == '"') {
+                src++;
+            }
+            buffer[dest] = buffer[src];
+            dest ++;
+            src ++;
+        }
+        bc.setEnd(dest);
+    }
+
+    /*
+    List of Separator Characters (see isSeparator())
+    Excluding the '/' char violates the RFC, but
+    it looks like a lot of people put '/'
+    in unquoted values: '/': ; //47
+    '\t':9 ' ':32 '\"':34 '\'':39 '(':40 ')':41 ',':44 ':':58 ';':59 '<':60
+    '=':61 '>':62 '?':63 '@':64 '[':91 '\\':92 ']':93 '{':123 '}':125
+    */
+    public static final char SEPARATORS[] = { '\t', ' ', '\"', '\'', '(', ')', ',',
+        ':', ';', '<', '=', '>', '?', '@', '[', '\\', ']', '{', '}' };
+
+    protected static final boolean separators[] = new boolean[128];
+    static {
+        for (int i = 0; i < 128; i++) {
+            separators[i] = false;
+        }
+        for (int i = 0; i < SEPARATORS.length; i++) {
+            separators[SEPARATORS[i]] = true;
+        }
+    }
+
+    /** Add all Cookie found in the headers of a request.
+     */
+    public  static void processCookies(List<ServerCookie> cookies,
+            List<ServerCookie> cookiesCache,
+            HttpMessage.HttpMessageBytes msgBytes ) {
+
+        // process each "cookie" header
+        for (int i = 0; i < msgBytes.headerCount; i++) {
+            if (msgBytes.getHeaderName(i).equalsIgnoreCase("Cookie")) {
+                BBuffer bc = msgBytes.getHeaderValue(i);
+                if (bc.remaining() == 0) {
+                    continue;
+                }
+                processCookieHeader(cookies, cookiesCache,
+                        bc.array(),
+                        bc.getOffset(),
+                        bc.getLength());
+
+            }
+
+        }
+    }
+
+    /**
+     * Returns true if the byte is a separator character as
+     * defined in RFC2619. Since this is called often, this
+     * function should be organized with the most probable
+     * outcomes first.
+     * JVK
+     */
+    private static final boolean isSeparator(final byte c) {
+         if (c > 0 && c < 126)
+             return separators[c];
+         else
+             return false;
+    }
+
+    /**
+     * Returns true if the byte is a whitespace character as
+     * defined in RFC2619
+     * JVK
+     */
+    private static final boolean isWhiteSpace(final byte c) {
+        // This switch statement is slightly slower
+        // for my vm than the if statement.
+        // Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_07-164)
+        /*
+        switch (c) {
+        case ' ':;
+        case '\t':;
+        case '\n':;
+        case '\r':;
+        case '\f':;
+            return true;
+        default:;
+            return false;
+        }
+        */
+       if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f')
+           return true;
+       else
+           return false;
+    }
+
+    /**
+     * Parses a cookie header after the initial "Cookie:"
+     * [WS][$]token[WS]=[WS](token|QV)[;|,]
+     * RFC 2965
+     * JVK
+     */
+    public static final void processCookieHeader(
+            List<ServerCookie> cookies,
+            List<ServerCookie> cookiesCache,
+            byte bytes[], int off, int len){
+        if( len<=0 || bytes==null ) return;
+        int end=off+len;
+        int pos=off;
+        int nameStart=0;
+        int nameEnd=0;
+        int valueStart=0;
+        int valueEnd=0;
+        int version = 0;
+        ServerCookie sc=null;
+        boolean isSpecial;
+        boolean isQuoted;
+
+        while (pos < end) {
+            isSpecial = false;
+            isQuoted = false;
+
+            // Skip whitespace and non-token characters (separators)
+            while (pos < end &&
+                   (isSeparator(bytes[pos]) || isWhiteSpace(bytes[pos])))
+                {pos++; }
+
+            if (pos >= end)
+                return;
+
+            // Detect Special cookies
+            if (bytes[pos] == '$') {
+                isSpecial = true;
+                pos++;
+            }
+
+            // Get the cookie name. This must be a token
+            valueEnd = valueStart = nameStart = pos;
+            pos = nameEnd = getTokenEndPosition(bytes,pos,end);
+
+            // Skip whitespace
+            while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }
+
+
+            // Check for an '=' -- This could also be a name-only
+            // cookie at the end of the cookie header, so if we
+            // are past the end of the header, but we have a name
+            // skip to the name-only part.
+            if (pos < end && bytes[pos] == '=') {
+
+                // Skip whitespace
+                do {
+                    pos++;
+                } while (pos < end && isWhiteSpace(bytes[pos]));
+
+                if (pos >= end)
+                    return;
+
+                // Determine what type of value this is, quoted value,
+                // token, name-only with an '=', or other (bad)
+                switch (bytes[pos]) {
+                case '"': // Quoted Value
+                    isQuoted = true;
+                    valueStart=pos + 1; // strip "
+                    // getQuotedValue returns the position before
+                    // at the last qoute. This must be dealt with
+                    // when the bytes are copied into the cookie
+                    valueEnd=getQuotedValueEndPosition(bytes,
+                                                       valueStart, end);
+                    // We need pos to advance
+                    pos = valueEnd;
+                    // Handles cases where the quoted value is
+                    // unterminated and at the end of the header,
+                    // e.g. [myname="value]
+                    if (pos >= end)
+                        return;
+                    break;
+                case ';':
+                case ',':
+                    // Name-only cookie with an '=' after the name token
+                    // This may not be RFC compliant
+                    valueStart = valueEnd = -1;
+                    // The position is OK (On a delimiter)
+                    break;
+                default:
+                    if (!isSeparator(bytes[pos])) {
+                        // Token
+                        valueStart=pos;
+                        // getToken returns the position at the delimeter
+                        // or other non-token character
+                        valueEnd=getTokenEndPosition(bytes, valueStart, end);
+                        // We need pos to advance
+                        pos = valueEnd;
+                    } else  {
+                        // INVALID COOKIE, advance to next delimiter
+                        // The starting character of the cookie value was
+                        // not valid.
+                        //log("Invalid cookie. Value not a token or quoted value");
+                        while (pos < end && bytes[pos] != ';' &&
+                               bytes[pos] != ',')
+                            {pos++; }
+                        pos++;
+                        // Make sure no special avpairs can be attributed to
+                        // the previous cookie by setting the current cookie
+                        // to null
+                        sc = null;
+                        continue;
+                    }
+                }
+            } else {
+                // Name only cookie
+                valueStart = valueEnd = -1;
+                pos = nameEnd;
+
+            }
+
+            // We should have an avpair or name-only cookie at this
+            // point. Perform some basic checks to make sure we are
+            // in a good state.
+
+            // Skip whitespace
+            while (pos < end && isWhiteSpace(bytes[pos])) {pos++; }
+
+
+            // Make sure that after the cookie we have a separator. This
+            // is only important if this is not the last cookie pair
+            while (pos < end && bytes[pos] != ';' && bytes[pos] != ',') {
+                pos++;
+            }
+
+            pos++;
+
+            /*
+            if (nameEnd <= nameStart || valueEnd < valueStart ) {
+                // Something is wrong, but this may be a case
+                // of having two ';' characters in a row.
+                // log("Cookie name/value does not conform to RFC 2965");
+                // Advance to next delimiter (ignoring everything else)
+                while (pos < end && bytes[pos] != ';' && bytes[pos] != ',')
+                    { pos++; };
+                pos++;
+                // Make sure no special cookies can be attributed to
+                // the previous cookie by setting the current cookie
+                // to null
+                sc = null;
+                continue;
+            }
+            */
+
+            // All checks passed. Add the cookie, start with the
+            // special avpairs first
+            if (isSpecial) {
+                isSpecial = false;
+                // $Version must be the first avpair in the cookie header
+                // (sc must be null)
+                if (equals( "Version", bytes, nameStart, nameEnd) &&
+                    sc == null) {
+                    // Set version
+                    if( bytes[valueStart] =='1' && valueEnd == (valueStart+1)) {
+                        version=1;
+                    } else {
+                        // unknown version (Versioning is not very strict)
+                    }
+                    continue;
+                }
+
+                // We need an active cookie for Path/Port/etc.
+                if (sc == null) {
+                    continue;
+                }
+
+                // Domain is more common, so it goes first
+                if (equals( "Domain", bytes, nameStart, nameEnd)) {
+                    sc.getDomain().setBytes( bytes,
+                                           valueStart,
+                                           valueEnd-valueStart);
+                    continue;
+                }
+
+                if (equals( "Path", bytes, nameStart, nameEnd)) {
+                    sc.getPath().setBytes( bytes,
+                                           valueStart,
+                                           valueEnd-valueStart);
+                    continue;
+                }
+
+
+                if (equals( "Port", bytes, nameStart, nameEnd)) {
+                    // sc.getPort is not currently implemented.
+                    // sc.getPort().setBytes( bytes,
+                    //                        valueStart,
+                    //                        valueEnd-valueStart );
+                    continue;
+                }
+
+                // Unknown cookie, complain
+                //log("Unknown Special Cookie");
+
+            } else { // Normal Cookie
+                // use a previous value from cache, if any (to avoid GC - tomcat
+                // legacy )
+                if (cookiesCache.size() > cookies.size()) {
+                    sc = cookiesCache.get(cookies.size());
+                    cookies.add(sc);
+                } else {
+                    sc = new ServerCookie();
+                    cookiesCache.add(sc);
+                    cookies.add(sc);
+                }
+                sc.setVersion( version );
+                sc.getName().append( bytes, nameStart,
+                                       nameEnd-nameStart);
+
+                if (valueStart != -1) { // Normal AVPair
+                    sc.getValue().append( bytes, valueStart,
+                            valueEnd-valueStart);
+                    if (isQuoted) {
+                        // We know this is a byte value so this is safe
+                        ServerCookie.unescapeDoubleQuotes(
+                                sc.getValue());
+                    }
+                } else {
+                    // Name Only
+                    sc.getValue().recycle();
+                }
+                sc.nameC.recycle();
+                sc.nameC.append(sc.getName());
+                continue;
+            }
+        }
+    }
+
+    /**
+     * Given the starting position of a token, this gets the end of the
+     * token, with no separator characters in between.
+     * JVK
+     */
+    private static final int getTokenEndPosition(byte bytes[], int off, int end){
+        int pos = off;
+        while (pos < end && !isSeparator(bytes[pos])) {pos++; }
+
+        if (pos > end)
+            return end;
+        return pos;
+    }
+
+    /**
+     * Given a starting position after an initial quote chracter, this gets
+     * the position of the end quote. This escapes anything after a '\' char
+     * JVK RFC 2616
+     */
+    private static final int getQuotedValueEndPosition(byte bytes[], int off, int end){
+        int pos = off;
+        while (pos < end) {
+            if (bytes[pos] == '"') {
+                return pos;
+            } else if (bytes[pos] == '\\' && pos < (end - 1)) {
+                pos+=2;
+            } else {
+                pos++;
+            }
+        }
+        // Error, we have reached the end of the header w/o a end quote
+        return end;
+    }
+
+
+    public static boolean equals( String s, byte b[], int start, int end) {
+        int blen = end-start;
+        if (b == null || blen != s.length()) {
+            return false;
+        }
+        int boff = start;
+        for (int i = 0; i < blen; i++) {
+            if (b[boff++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+}
+
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java
new file mode 100644
index 0000000..af4e200
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/SpdyConnection.java
@@ -0,0 +1,820 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer;
+import org.apache.tomcat.lite.http.HttpMessage.HttpMessageBytes;
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.DumpChannel;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+/*
+ * TODO: expectations ?
+ * Fix docs - order matters
+ * Crashes in chrome
+ *
+ * Test with unit tests or:
+ *  google-chrome --use-flip=no-ssl
+ *    --user-data-dir=/home/$USER/.config/google-chrome/Test
+ *    http://localhost:8802/hello
+ */
+
+public class SpdyConnection extends HttpConnector.HttpConnection
+        implements IOConnector.ConnectedCallback {
+
+
+    /** Use compression for headers. Will magically turn to false
+     * if the first request doesn't have x8xx ( i.e. compress header )
+     */
+    boolean headerCompression = true;
+    boolean firstFrame = true;
+
+    public static long DICT_ID = 3751956914L;
+    private static String SPDY_DICT_S =
+        "optionsgetheadpostputdeletetraceacceptaccept-charsetaccept-encodingaccept-" +
+        "languageauthorizationexpectfromhostif-modified-sinceif-matchif-none-matchi" +
+        "f-rangeif-unmodifiedsincemax-forwardsproxy-authorizationrangerefererteuser" +
+        "-agent10010120020120220320420520630030130230330430530630740040140240340440" +
+        "5406407408409410411412413414415416417500501502503504505accept-rangesageeta" +
+        "glocationproxy-authenticatepublicretry-afterservervarywarningwww-authentic" +
+        "ateallowcontent-basecontent-encodingcache-controlconnectiondatetrailertran" +
+        "sfer-encodingupgradeviawarningcontent-languagecontent-lengthcontent-locati" +
+        "oncontent-md5content-rangecontent-typeetagexpireslast-modifiedset-cookieMo" +
+        "ndayTuesdayWednesdayThursdayFridaySaturdaySundayJanFebMarAprMayJunJulAugSe" +
+        "pOctNovDecchunkedtext/htmlimage/pngimage/jpgimage/gifapplication/xmlapplic" +
+        "ation/xhtmltext/plainpublicmax-agecharset=iso-8859-1utf-8gzipdeflateHTTP/1" +
+        ".1statusversionurl ";
+    public static byte[] SPDY_DICT = SPDY_DICT_S.getBytes();
+    // C code uses this - not in the spec
+    static {
+        SPDY_DICT[SPDY_DICT.length - 1] = (byte) 0;
+    }
+
+
+    protected static Logger log = Logger.getLogger("SpdyConnection");
+
+    /**
+     * @param spdyConnector
+     * @param remoteServer
+     */
+    SpdyConnection(HttpConnector spdyConnector, RemoteServer remoteServer) {
+        this.httpConnector = spdyConnector;
+        this.remoteHost = remoteServer;
+        this.target = remoteServer.target;
+    }
+
+    AtomicInteger streamErrors = new AtomicInteger();
+
+    AtomicInteger lastInStream = new AtomicInteger();
+    AtomicInteger lastOutStream = new AtomicInteger();
+
+    // TODO: use int map
+    Map<Integer, HttpChannel> channels = new HashMap();
+
+    SpdyConnection.Frame currentInFrame = null;
+
+    SpdyConnection.Frame lastFrame = null; // for debug
+
+    BBuffer outFrameBuffer = BBuffer.allocate();
+    BBuffer inFrameBuffer = BBuffer.allocate();
+
+    BBuffer headW = BBuffer.wrapper();
+
+    CompressFilter headCompressIn = new CompressFilter()
+        .setDictionary(SPDY_DICT, DICT_ID);
+
+    CompressFilter headCompressOut = new CompressFilter()
+        .setDictionary(SPDY_DICT, DICT_ID);
+
+    IOBuffer headerCompressBuffer = new IOBuffer();
+    IOBuffer headerDeCompressBuffer = new IOBuffer();
+
+    AtomicInteger inFrames = new AtomicInteger();
+    AtomicInteger inDataFrames = new AtomicInteger();
+    AtomicInteger inSyncStreamFrames = new AtomicInteger();
+    AtomicInteger inBytes = new AtomicInteger();
+
+    AtomicInteger outFrames = new AtomicInteger();
+    AtomicInteger outDataFrames = new AtomicInteger();
+    AtomicInteger outBytes = new AtomicInteger();
+
+
+    volatile boolean connecting = false;
+    volatile boolean connected = false;
+
+    // TODO: detect if it's spdy or http based on bit 8
+
+    @Override
+    public void withExtraBuffer(BBuffer received) {
+        inFrameBuffer = received;
+    }
+
+    @Override
+    public void dataReceived(IOBuffer iob) throws IOException {
+        // Only one thread doing receive at a time.
+        synchronized (inFrameBuffer) {
+            while (true) {
+                int avail = iob.available();
+                if (avail == 0) {
+                    return;
+                }
+                if (currentInFrame == null) {
+                    if (inFrameBuffer.remaining() + avail < 8) {
+                        return;
+                    }
+                    if (inFrameBuffer.remaining() < 8) {
+                        int headRest = 8 - inFrameBuffer.remaining();
+                        int rd = iob.read(inFrameBuffer, headRest);
+                    }
+                    currentInFrame = new SpdyConnection.Frame(); // TODO: reuse
+                    currentInFrame.parse(this, inFrameBuffer);
+                }
+                if (iob.available() < currentInFrame.length) {
+                    return;
+                }
+                // We have a full frame. Process it.
+                onFrame(iob);
+
+                // TODO: extra checks, make sure the frame is correct and
+                // it consumed all data.
+                currentInFrame = null;
+            }
+        }
+    }
+
+
+    /**
+     * Frame received. Must consume all data for the frame.
+     *
+     * @param iob
+     * @throws IOException
+     */
+    protected void onFrame(IOBuffer iob) throws IOException {
+        // TODO: make sure we have enough data.
+        lastFrame = currentInFrame;
+        inFrames.incrementAndGet();
+        inBytes.addAndGet(currentInFrame.length + 8);
+
+        if (currentInFrame.c) {
+            if (currentInFrame.type == SpdyConnection.Frame.TYPE_HELO) {
+                // receivedHello = currentInFrame;
+            } else if (currentInFrame.type == SpdyConnection.Frame.TYPE_SYN_STREAM) {
+                inSyncStreamFrames.incrementAndGet();
+                HttpChannel ch = new HttpChannel(); // TODO: reuse
+                ch.channelId = SpdyConnection.readInt(iob);
+                ch.setConnection(this);
+                ch.httpConnector = this.httpConnector;
+                if (serverMode) {
+                    ch.serverMode(true);
+                }
+                if (this.httpConnector.defaultService != null) {
+                    ch.setHttpService(this.httpConnector.defaultService);
+                }
+
+                synchronized (channels) {
+                        channels.put(ch.channelId, ch);
+                }
+
+                try {
+                    // pri and unused
+                    SpdyConnection.readShort(iob);
+
+                    HttpMessageBytes reqBytes = ch.getRequest().getMsgBytes();
+
+                    processHeaders(iob, ch, reqBytes);
+                } catch (Throwable t) {
+                    log.log(Level.SEVERE, "Error parsing head", t);
+                    abort("Error reading headers " + t);
+                    return;
+                }
+                ch.getRequest().processReceivedHeaders();
+
+                ch.handleHeadersReceived(ch.getRequest());
+
+                if ((currentInFrame.flags & SpdyConnection.Frame.FLAG_HALF_CLOSE) != 0) {
+                    ch.getIn().close();
+                    ch.handleEndReceive();
+                }
+            } else if (currentInFrame.type == SpdyConnection.Frame.TYPE_SYN_REPLY) {
+                int chId = SpdyConnection.readInt(iob);
+                HttpChannel ch;
+                synchronized (channels) {
+                    ch = channels.get(chId);
+                    if (ch == null) {
+                        abort("Channel not found");
+                    }
+                }
+                try {
+                    SpdyConnection.readShort(iob);
+
+                    HttpMessageBytes resBytes = ch.getResponse().getMsgBytes();
+
+                    BBuffer head = processHeaders(iob, ch, resBytes);
+                } catch (Throwable t) {
+                    log.log(Level.SEVERE, "Error parsing head", t);
+                    abort("Error reading headers " + t);
+                    return;
+                }
+                ch.getResponse().processReceivedHeaders();
+
+                ch.handleHeadersReceived(ch.getResponse());
+
+                if ((currentInFrame.flags & SpdyConnection.Frame.FLAG_HALF_CLOSE) != 0) {
+                    ch.getIn().close();
+                    ch.handleEndReceive();
+                }
+            } else {
+                log.warning("Unknown frame type " + currentInFrame.type);
+                iob.advance(currentInFrame.length);
+            }
+        } else {
+            inDataFrames.incrementAndGet();
+            // data frame - part of an existing stream
+            HttpChannel ch;
+            synchronized (channels) {
+                ch = channels.get(currentInFrame.streamId);
+            }
+            if (ch == null) {
+                log.warning("Unknown stream ");
+                net.close();
+                net.startSending();
+                return;
+            }
+            int len = currentInFrame.length;
+            while (len > 0) {
+                BBucket bb = iob.peekFirst();
+                if (bb == null) {
+                    // we should have all data
+                    abort("Unexpected short read");
+                    return;
+                }
+                if (len > bb.remaining()) {
+                    ch.getIn().append(bb);
+                    len -= bb.remaining();
+                    bb.position(bb.limit());
+                } else {
+                    ch.getIn().append(bb, len);
+                    bb.position(bb.position() + len);
+                    len = 0;
+                }
+            }
+            ch.sendHandleReceivedCallback();
+
+            if ((currentInFrame.flags & SpdyConnection.Frame.FLAG_HALF_CLOSE) != 0) {
+                ch.handleEndReceive();
+            }
+        }
+        firstFrame = false;
+    }
+
+    /**
+     * On frame error.
+     */
+    private void abort(String msg) throws IOException {
+        streamErrors.incrementAndGet();
+        synchronized(channels) {
+            for (HttpChannel ch : channels.values()) {
+                ch.abort(msg);
+            }
+        }
+        close();
+    }
+
+    private BBuffer processHeaders(IOBuffer iob, HttpChannel ch,
+            HttpMessageBytes reqBytes) throws IOException {
+        int nvCount = 0;
+        if (firstFrame) {
+            int res = iob.peek() & 0xFF;
+            if ((res & 0x0F) !=  8) {
+                headerCompression = false;
+            }
+        }
+        headRecvBuf.recycle();
+        if (headerCompression) {
+            // 0x800 headers seems a bit too much - assume compressed.
+            // I wish this was a flag...
+            headerDeCompressBuffer.recycle();
+            // stream id ( 4 ) + unused ( 2 )
+            // nvCount is compressed in impl - spec is different
+            headCompressIn.decompress(iob, headerDeCompressBuffer,
+                    currentInFrame.length - 6);
+            headerDeCompressBuffer.copyAll(headRecvBuf);
+            headerDeCompressBuffer.recycle();
+            nvCount = readShort(headRecvBuf);
+        } else {
+            nvCount = readShort(iob);
+            // 8 = stream Id (4) + pri/unused (2) + nvCount (2)
+            // we know we have enough data
+            int rd = iob.read(headRecvBuf, currentInFrame.length - 8);
+            if (rd != currentInFrame.length - 8) {
+                abort("Unexpected incomplete read");
+            }
+        }
+        // Wrapper - so we don't change position in head
+        headRecvBuf.wrapTo(headW);
+
+        BBuffer nameBuf = BBuffer.wrapper();
+        BBuffer valBuf = BBuffer.wrapper();
+
+        for (int i = 0; i < nvCount; i++) {
+
+            int nameLen = SpdyConnection.readShort(headW);
+            if (nameLen > headW.remaining()) {
+                abort("Name too long");
+            }
+
+            nameBuf.setBytes(headW.array(), headW.position(),
+                            nameLen);
+            headW.advance(nameLen);
+
+            int valueLen = SpdyConnection.readShort(headW);
+            valBuf.setBytes(headW.array(), headW.position(),
+                            valueLen);
+            headW.advance(valueLen);
+
+            // TODO: no need to send version, method if default
+
+            if (nameBuf.equals("method")) {
+                valBuf.wrapTo(reqBytes.method());
+            } else if (nameBuf.equals("version")) {
+                valBuf.wrapTo(reqBytes.protocol());
+            } else if (nameBuf.equals("url")) {
+                valBuf.wrapTo(reqBytes.url());
+                // TODO: spdy uses full URL, we may want to trim
+                // also no host header
+            } else {
+                int idx = reqBytes.addHeader();
+                nameBuf.wrapTo(reqBytes.getHeaderName(idx));
+                valBuf.wrapTo(reqBytes.getHeaderValue(idx));
+            }
+
+            // TODO: repeated values are separated by a 0
+        }
+        return headW;
+    }
+
+    @Override
+    protected synchronized void sendRequest(HttpChannel http) throws IOException {
+        if (serverMode) {
+            throw new IOException("Only in client mode");
+        }
+        if (!checkConnection(http)) {
+            return;
+        }
+        MultiMap mimeHeaders = http.getRequest().getMimeHeaders();
+
+        BBuffer headBuf = BBuffer.allocate();
+        SpdyConnection.appendShort(headBuf, mimeHeaders.size() + 3);
+        serializeMime(mimeHeaders, headBuf);
+
+        // TODO: url - with host prefix , method
+        // optimize...
+        SpdyConnection.appendAsciiHead(headBuf, "version");
+        SpdyConnection.appendAsciiHead(headBuf, "HTTP/1.1");
+
+        SpdyConnection.appendAsciiHead(headBuf, "method");
+        SpdyConnection.appendAsciiHead(headBuf, http.getRequest().getMethod());
+
+        SpdyConnection.appendAsciiHead(headBuf, "url");
+        // TODO: url
+        SpdyConnection.appendAsciiHead(headBuf, http.getRequest().requestURL());
+
+        if (headerCompression && httpConnector.compression) {
+            headerCompressBuffer.recycle();
+            headCompressOut.compress(headBuf, headerCompressBuffer, false);
+            headBuf.recycle();
+            headerCompressBuffer.copyAll(headBuf);
+        }
+
+        // Frame head - 8
+        BBuffer out = BBuffer.allocate();
+        // Syn-reply
+        out.putByte(0x80);
+        out.putByte(0x01);
+        out.putByte(0x00);
+        out.putByte(0x01);
+
+        CBuffer method = http.getRequest().method();
+        if (method.equals("GET") || method.equals("HEAD")) {
+            http.getOut().close();
+        }
+
+        if (http.getOut().isAppendClosed()) {
+            out.putByte(0x01); // closed
+        } else {
+            out.putByte(0x00);
+        }
+
+        // Length, channel id (4) + unused (2) - headBuf has header count
+        // and headers
+        SpdyConnection.append24(out, headBuf.remaining() + 6);
+
+        if (serverMode) {
+            http.channelId = 2 * lastOutStream.incrementAndGet();
+        } else {
+            http.channelId = 2 * lastOutStream.incrementAndGet() + 1;
+        }
+        SpdyConnection.appendInt(out, http.channelId);
+
+        http.setConnection(this);
+
+        synchronized (channels) {
+            channels.put(http.channelId, http);
+        }
+
+        out.putByte(0x00); // no priority
+        out.putByte(0x00);
+
+        sendFrame(out, headBuf);
+
+        if (http.outMessage.state == HttpMessage.State.HEAD) {
+            http.outMessage.state = HttpMessage.State.BODY_DATA;
+        }
+        if (http.getOut().isAppendClosed()) {
+            http.handleEndSent();
+        }
+
+        // Any existing data
+        //sendData(http);
+    }
+
+
+    public synchronized Collection<HttpChannel> getActives() {
+        synchronized(channels) {
+            return channels.values();
+        }
+    }
+
+    @Override
+    protected synchronized void sendResponseHeaders(HttpChannel http) throws IOException {
+        if (!serverMode) {
+            throw new IOException("Only in server mode");
+        }
+
+        if (http.getResponse().isCommitted()) {
+            return;
+        }
+        http.getResponse().setCommitted(true);
+
+        MultiMap mimeHeaders = http.getResponse().getMimeHeaders();
+
+        BBuffer headBuf = BBuffer.allocate();
+
+
+        //mimeHeaders.remove("content-length");
+        BBuffer headers = headBuf;
+        if (headerCompression) {
+            headers = BBuffer.allocate();
+        }
+
+        //SpdyConnection.appendInt(headers, http.channelId);
+        //headers.putByte(0);
+        //headers.putByte(0);
+        SpdyConnection.appendShort(headers, mimeHeaders.size() + 2);
+
+        // chrome will crash if we don't send the header
+        serializeMime(mimeHeaders, headers);
+
+        // Must be at the end
+        SpdyConnection.appendAsciiHead(headers, "status");
+        SpdyConnection.appendAsciiHead(headers,
+                Integer.toString(http.getResponse().getStatus()));
+
+        SpdyConnection.appendAsciiHead(headers, "version");
+        SpdyConnection.appendAsciiHead(headers, "HTTP/1.1");
+
+        if (headerCompression) {
+            headerCompressBuffer.recycle();
+            headCompressOut.compress(headers, headerCompressBuffer, false);
+            headerCompressBuffer.copyAll(headBuf);
+            headerCompressBuffer.recycle();
+        }
+
+        BBuffer frameHead = BBuffer.allocate();
+        // Syn-reply
+        frameHead.putByte(0x80); // Control
+        frameHead.putByte(0x01); // version
+        frameHead.putByte(0x00); // 00 02 - SYN_REPLY
+        frameHead.putByte(0x02);
+
+        // It seems piggibacking data is not allowed
+        frameHead.putByte(0x00);
+
+        int len = headBuf.remaining() + 6;
+        SpdyConnection.append24(frameHead, len);
+
+//        // Stream-Id, unused
+        SpdyConnection.appendInt(frameHead, http.channelId);
+        frameHead.putByte(0);
+        frameHead.putByte(0);
+
+        sendFrame(frameHead, headBuf);
+    }
+
+
+    public void startSending(HttpChannel http) throws IOException {
+        http.send(); // if needed
+
+        if (net != null) {
+            sendData(http);
+            net.startSending();
+        }
+    }
+
+    private void sendData(HttpChannel http) throws IOException {
+        int avail = http.getOut().available();
+        boolean closed = http.getOut().isAppendClosed();
+        if (avail > 0 || closed) {
+            sendDataFrame(http.getOut(), avail,
+                    http.channelId, closed);
+            if (avail > 0) {
+                getOut().advance(avail);
+            }
+        }
+        if (closed) {
+            http.handleEndSent();
+        }
+    }
+
+    private BBuffer serializeMime(MultiMap mimeHeaders, BBuffer headBuf)
+            throws IOException {
+
+        // TODO: duplicated headers not allowed
+        for (int i = 0; i < mimeHeaders.size(); i++) {
+            CBuffer name = mimeHeaders.getName(i);
+            CBuffer value = mimeHeaders.getValue(i);
+            if (name.length() == 0 || value.length() == 0) {
+                continue;
+            }
+            SpdyConnection.appendShort(headBuf, name.length());
+            name.toAscii(headBuf);
+            SpdyConnection.appendShort(headBuf, value.length());
+            value.toAscii(headBuf);
+        }
+        return headBuf;
+    }
+
+
+    private synchronized void sendFrame(BBuffer out, BBuffer headBuf)
+            throws IOException {
+        if (net == null) {
+            return; // unit test
+        }
+        outBytes.addAndGet(out.remaining());
+        net.getOut().append(out);
+        if (headBuf != null) {
+            net.getOut().append(headBuf);
+            outBytes.addAndGet(headBuf.remaining());
+        }
+        net.startSending();
+        outFrames.incrementAndGet();
+    }
+
+    public synchronized void sendDataFrame(IOBuffer out2, int avail,
+            int channelId, boolean last) throws IOException {
+        if (net == null) {
+            return; // unit test
+        }
+        outFrameBuffer.recycle();
+        SpdyConnection.appendInt(outFrameBuffer, channelId); // first bit 0 ?
+        if (last) {
+            outFrameBuffer.putByte(0x01); // closed
+        } else {
+            outFrameBuffer.putByte(0x00);
+        }
+
+        // TODO: chunk if too much data ( at least at 24 bits)
+        SpdyConnection.append24(outFrameBuffer, avail);
+
+        outBytes.addAndGet(outFrameBuffer.remaining() + avail);
+        net.getOut().append(outFrameBuffer);
+
+        if (avail > 0) {
+            net.getOut().append(out2, avail);
+        }
+        net.startSending();
+        outDataFrames.incrementAndGet();
+    }
+
+    static void appendInt(BBuffer headBuf, int length) throws IOException {
+        headBuf.putByte((length & 0xFF000000) >> 24);
+        headBuf.putByte((length & 0xFF0000) >> 16);
+        headBuf.putByte((length & 0xFF00) >> 8);
+        headBuf.putByte((length & 0xFF));
+    }
+
+    static void append24(BBuffer headBuf, int length) throws IOException {
+        headBuf.putByte((length & 0xFF0000) >> 16);
+        headBuf.putByte((length & 0xFF00) >> 8);
+        headBuf.putByte((length & 0xFF));
+    }
+
+    static void appendAsciiHead(BBuffer headBuf, CBuffer s) throws IOException {
+        appendShort(headBuf, s.length());
+        for (int i = 0; i < s.length(); i++) {
+            headBuf.append(s.charAt(i));
+        }
+    }
+
+    static void appendShort(BBuffer headBuf, int length) throws IOException {
+        if (length > 0xFFFF) {
+            throw new IOException("Too long");
+        }
+        headBuf.putByte((length & 0xFF00) >> 8);
+        headBuf.putByte((length & 0xFF));
+    }
+
+    static void appendAsciiHead(BBuffer headBuf, String s) throws IOException {
+        SpdyConnection.appendShort(headBuf, s.length());
+        for (int i = 0; i < s.length(); i++) {
+            headBuf.append(s.charAt(i));
+        }
+    }
+
+    static int readShort(BBuffer iob) throws IOException {
+        int res = iob.readByte();
+        return res << 8 | iob.readByte();
+    }
+
+    static int readShort(IOBuffer iob) throws IOException {
+        int res = iob.read();
+        return res << 8 | iob.read();
+    }
+
+    static int readInt(IOBuffer iob) throws IOException {
+        int res = 0;
+        for (int i = 0; i < 4; i++) {
+            int b0 = iob.read();
+            res = res << 8 | b0;
+        }
+        return res;
+    }
+
+    public static class Frame {
+        int flags;
+
+        int length;
+
+        boolean c; // for control
+
+        int version;
+
+        int type;
+
+        int streamId; // for data
+
+        static int TYPE_HELO = 4;
+
+        static int TYPE_SYN_STREAM = 1;
+
+        static int TYPE_SYN_REPLY = 2;
+
+        static int FLAG_HALF_CLOSE = 1;
+
+        public void parse(SpdyConnection spdyConnection,
+                BBuffer iob) throws IOException {
+            int b0 = iob.read();
+            if (b0 < 128) {
+                // data frame
+                c = false;
+                streamId = b0;
+                for (int i = 0; i < 3; i++) {
+                    b0 = iob.read();
+                    streamId = streamId << 8 | b0;
+                }
+            } else {
+                c = true;
+                b0 -= 128;
+                version = ((b0 << 8) | iob.read());
+                if (version != 1) {
+                    spdyConnection.abort("Wrong version");
+                    return;
+                }
+                b0 = iob.read();
+                type = ((b0 << 8) | iob.read());
+            }
+
+            flags = iob.read();
+            for (int i = 0; i < 3; i++) {
+                b0 = iob.read();
+                length = length << 8 | b0;
+            }
+
+            iob.recycle();
+        }
+
+    }
+
+    @Override
+    protected void endSendReceive(HttpChannel http) throws IOException {
+        synchronized (channels) {
+            HttpChannel doneHttp = channels.remove(http.channelId);
+            if (doneHttp != http) {
+                log.severe("Error removing " + doneHttp + " " + http);
+            }
+        }
+        httpConnector.cpool.afterRequest(http, this, true);
+    }
+
+    /**
+     * Framing error, client interrupt, etc.
+     */
+    public void abort(HttpChannel http, String t) throws IOException {
+        // TODO: send interrupt signal
+
+    }
+
+
+    private boolean checkConnection(HttpChannel http) throws IOException {
+        synchronized(this) {
+            if (net == null || !isOpen()) {
+                connected = false;
+            }
+
+            if (!connected) {
+                if (!connecting) {
+                    // TODO: secure set at start ?
+                    connecting = true;
+                    httpConnector.cpool.httpConnect(http,
+                            target.toString(),
+                            http.getRequest().isSecure(), this);
+                }
+
+                synchronized (remoteHost) {
+                    remoteHost.pending.add(http);
+                    httpConnector.cpool.queued.incrementAndGet();
+                }
+                return false;
+            }
+        }
+
+        return true;
+    }
+
+    @Override
+    public void handleConnected(IOChannel net) throws IOException {
+        HttpChannel httpCh = null;
+        if (!net.isOpen()) {
+            while (true) {
+                synchronized (remoteHost) {
+                    if (remoteHost.pending.size() == 0) {
+                        return;
+                    }
+                    httpCh = remoteHost.pending.remove();
+                }
+                httpCh.abort("Can't connect");
+            }
+        }
+
+        synchronized (remoteHost) {
+            httpCh = remoteHost.pending.peek();
+        }
+        if (httpCh != null) {
+            secure = httpCh.getRequest().isSecure();
+            if (secure) {
+                if (httpConnector.debugHttp) {
+                    net = DumpChannel.wrap("SPDY-SSL", net);
+                }
+                String[] hostPort = httpCh.getTarget().split(":");
+
+                IOChannel ch1 = httpConnector.sslProvider.channel(net,
+                        hostPort[0], Integer.parseInt(hostPort[1]));
+                //net.setHead(ch1);
+                net = ch1;
+            }
+        }
+        if (httpConnector.debugHttp) {
+            net = DumpChannel.wrap("SPDY", net);
+        }
+
+        setSink(net);
+
+        synchronized(this) {
+            connecting = false;
+            connected = true;
+        }
+
+        while (true) {
+            synchronized (remoteHost) {
+                if (remoteHost.pending.size() == 0) {
+                    return;
+                }
+                httpCh = remoteHost.pending.remove();
+            }
+            sendRequest(httpCh);
+        }
+
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/http/package.html b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/package.html
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/http/package.html
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBucket.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBucket.java
new file mode 100644
index 0000000..560629b
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBucket.java
@@ -0,0 +1,41 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.nio.ByteBuffer;
+
+
+
+/**
+ * Holds raw data. Similar interface with a ByteBuffer in 'channel write'
+ * or 'read mode'. Data is between position and limit - there is no
+ * switching.
+ *
+ * TODO: FileBucket, DirectBufferBucket, CharBucket, ...
+ *
+ * @author Costin Manolache
+ */
+public interface BBucket {
+
+    public void release();
+
+    public byte[] array();
+    public int position();
+    public int remaining();
+    public int limit();
+
+    public boolean hasRemaining();
+
+    public void position(int newStart);
+
+    /**
+     * Return a byte buffer, with data between position and limit.
+     * Changes in the ByteBuffer position will not be reflected
+     * in the IOBucket.
+     *
+     * @return
+     */
+    public ByteBuffer getByteBuffer();
+
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java
new file mode 100644
index 0000000..e3c20fa
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BBuffer.java
@@ -0,0 +1,1204 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.nio.ByteBuffer;
+import java.nio.charset.Charset;
+
+/*
+ * In a server it is very important to be able to operate on
+ * the original byte[] without converting everything to chars.
+ * Some protocols are ASCII only, and some allow different
+ * non-UNICODE encodings. The encoding is not known beforehand,
+ * and can even change during the execution of the protocol.
+ * ( for example a multipart message may have parts with different
+ *  encoding )
+ *
+ * For HTTP it is not very clear how the encoding of RequestURI
+ * and mime values can be determined, but it is a great advantage
+ * to be able to parse the request without converting to string.
+ */
+
+// Renamed from ByteChunk to make it easier to write code using both
+
+/**
+ * This class is used to represent a chunk of bytes, and utilities to manipulate
+ * byte[].
+ *
+ * The buffer can be modified and used for both input and output.
+ *
+ * There are 2 modes: The chunk can be associated with a sink - ByteInputChannel
+ * or ByteOutputChannel, which will be used when the buffer is empty ( on input
+ * ) or filled ( on output ). For output, it can also grow. This operating mode
+ * is selected by calling setLimit() or allocate(initial, limit) with limit !=
+ * -1.
+ *
+ * Various search and append method are defined - similar with String and
+ * StringBuffer, but operating on bytes.
+ *
+ * This is important because it allows processing the http headers directly on
+ * the received bytes, without converting to chars and Strings until the strings
+ * are needed. In addition, the charset is determined later, from headers or
+ * user code.
+ *
+ *
+ * @author dac@sun.com
+ * @author James Todd [gonzo@sun.com]
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public class BBuffer implements Cloneable, Serializable,
+    BBucket {
+
+    /**
+     * Default encoding used to convert to strings. It should be UTF8, but:
+     * - the servlet API requires 8859_1 as default
+     * -
+     */
+    public static final String DEFAULT_CHARACTER_ENCODING = "ISO-8859-1";
+
+    // byte[]
+    private byte[] buff;
+
+    private int start = 0;
+
+    private int end;
+
+    private ByteBuffer byteBuffer;
+
+    public static final String CRLF = "\r\n";
+
+    /* Various constant "strings" */
+    public static final byte[] CRLF_BYTES = convertToBytes(BBuffer.CRLF);
+
+    /**
+     * HT.
+     */
+    public static final byte HT = (byte) '\t';
+
+    /**
+     * SP.
+     */
+    public static final byte SP = (byte) ' ';
+
+    /**
+     * LF.
+     */
+    public static final byte LF = (byte) '\n';
+
+    /**
+     * CR.
+     */
+    public static final byte CR = (byte) '\r';
+
+    //private int useCount;
+
+
+    private static final boolean[] isDigit = new boolean[256];
+
+    static Charset UTF8;
+
+    public static final byte A = (byte) 'A';
+
+    public static final byte Z = (byte) 'Z';
+
+    public static final byte a = (byte) 'a';
+
+    public static final byte LC_OFFSET = A - a;
+    private static final byte[] toLower = new byte[256];
+    private static final boolean[] isUpper = new boolean[256];
+
+    static {
+        for (int i = 0; i < 256; i++) {
+            toLower[i] = (byte)i;
+        }
+
+        for (int lc = 'a'; lc <= 'z'; lc++) {
+            int uc = lc + 'A' - 'a';
+            toLower[uc] = (byte)lc;
+            isUpper[uc] = true;
+        }
+    }
+
+    static {
+        for (int d = '0'; d <= '9'; d++) {
+            isDigit[d] = true;
+        }
+        UTF8 = Charset.forName("UTF-8");
+    }
+
+    public static BBuffer allocate() {
+        return new BBuffer();
+    }
+
+    public static BBuffer allocate(int initial) {
+        return new BBuffer().makeSpace(initial);
+    }
+
+
+    public static BBuffer allocate(String msg) {
+        BBuffer bc = allocate();
+        byte[] data = msg.getBytes();
+        bc.append(data, 0, data.length);
+        return bc;
+    }
+
+    public static BBuffer wrapper(String msg) {
+        BBuffer bc = new IOBucketWrap();
+        byte[] data = msg.getBytes();
+        bc.setBytes(data, 0, data.length);
+        return bc;
+    }
+
+    public static BBuffer wrapper() {
+        return new IOBucketWrap();
+    }
+
+    public static BBuffer wrapper(BBuffer bb) {
+        BBuffer res = new IOBucketWrap();
+        res.setBytes(bb.array(), bb.position(), bb.remaining());
+        return res;
+    }
+
+    public static BBuffer wrapper(byte b[], int off, int len) {
+        BBuffer res = new IOBucketWrap();
+        res.setBytes(b, off, len);
+        return res;
+    }
+
+    public static BBuffer wrapper(BBucket bb, int start, int len) {
+        BBuffer res = new IOBucketWrap();
+        res.setBytes(bb.array(), bb.position() + start, len);
+        return res;
+    }
+
+    /**
+     * Creates a new, uninitialized ByteChunk object.
+     */
+    private BBuffer() {
+    }
+
+    public void append(BBuffer src) {
+        append(src.array(), src.getStart(), src.getLength());
+    }
+
+    /**
+     * Add data to the buffer
+     */
+    public void append(byte src[], int off, int len) {
+        // will grow, up to limit
+        makeSpace(len);
+
+        // assert: makeSpace made enough space
+        System.arraycopy(src, off, buff, end, len);
+        end += len;
+        return;
+    }
+
+    // -------------------- Adding data to the buffer --------------------
+    /**
+     * Append a char, by casting it to byte. This IS NOT intended for unicode.
+     *
+     * @param c
+     */
+    public void append(char c) {
+        put((byte) c);
+    }
+
+    // -------------------- Removing data from the buffer --------------------
+
+    /**
+     * Returns the message bytes.
+     */
+    @Override
+    public byte[] array() {
+        return buff;
+    }
+
+    public int capacity() {
+        return buff.length;
+    }
+
+    public boolean equals(BBuffer bb) {
+        return equals(bb.array(), bb.getStart(), bb.getLength());
+    }
+
+    public boolean equals(byte b2[], int off2, int len2) {
+        byte b1[] = buff;
+        if (b1 == null && b2 == null)
+            return true;
+
+        int len = end - start;
+        if (len2 != len || b1 == null || b2 == null)
+            return false;
+
+        int off1 = start;
+
+        while (len-- > 0) {
+            if (b1[off1++] != b2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    public boolean equals(char c2[], int off2, int len2) {
+        // XXX works only for enc compatible with ASCII/UTF !!!
+        byte b1[] = buff;
+        if (c2 == null && b1 == null)
+            return true;
+
+        if (b1 == null || c2 == null || end - start != len2) {
+            return false;
+        }
+        int off1 = start;
+        int len = end - start;
+
+        while (len-- > 0) {
+            if ((char) b1[off1++] != c2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    // -------------------- Conversion and getters --------------------
+
+    /**
+     * Compares the message bytes to the specified String object.
+     *
+     * @param s
+     *            the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equals(String s) {
+        // XXX ENCODING - this only works if encoding is UTF8-compat
+        // ( ok for tomcat, where we compare ascii - header names, etc )!!!
+
+        byte[] b = buff;
+        int blen = end - start;
+        if (b == null || blen != s.length()) {
+            return false;
+        }
+        int boff = start;
+        for (int i = 0; i < blen; i++) {
+            if (b[boff++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compares the message bytes to the specified String object.
+     *
+     * @param s
+     *            the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equalsIgnoreCase(String s) {
+        byte[] b = buff;
+        int blen = end - start;
+        if (b == null || blen != s.length()) {
+            return false;
+        }
+        int boff = start;
+        for (int i = 0; i < blen; i++) {
+            if (toLower(b[boff++]) != toLower(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public int get(int off) {
+        if (start + off >= end) {
+            throw new ArrayIndexOutOfBoundsException();
+        }
+        return buff[start + off] & 0xFF;
+    }
+
+    /**
+     * Return a byte buffer. Changes in the ByteBuffer position will
+     * not be reflected in the IOBucket
+     * @return
+     */
+    public ByteBuffer getByteBuffer() {
+        if (byteBuffer == null || byteBuffer.array() != buff) {
+            byteBuffer = ByteBuffer.wrap(buff, start, end - start);
+        } else {
+            byteBuffer.position(start);
+            byteBuffer.limit(end);
+        }
+        return byteBuffer;
+    }
+
+    // --------------------
+    public BBuffer getClone() {
+        try {
+            return (BBuffer) this.clone();
+        } catch (Exception ex) {
+            return null;
+        }
+    }
+
+    public int getEnd() {
+        return end;
+    }
+
+    public int getInt() {
+        return parseInt(buff, start, end - start);
+    }
+    /**
+     * Returns the length of the bytes. XXX need to clean this up
+     */
+    public int getLength() {
+        return end - start;
+    }
+
+    public long getLong() {
+        return parseLong(buff, start, end - start);
+    }
+
+    public int getOffset() {
+        return start;
+    }
+
+    // -------------------- equals --------------------
+
+    /**
+     * Returns the start offset of the bytes. For output this is the end of the
+     * buffer.
+     */
+    public int getStart() {
+        return start;
+    }
+
+    public ByteBuffer getWriteByteBuffer(int space) {
+        if (space == 0) {
+            space = 16;
+        }
+        makeSpace(space);
+        if (byteBuffer == null || byteBuffer.array() != buff) {
+            byteBuffer = ByteBuffer.wrap(buff, end, buff.length);
+        } else {
+            byteBuffer.position(end);
+            byteBuffer.limit(buff.length);
+        }
+        return byteBuffer;
+    }
+
+    // -------------------- Hash code --------------------
+    public int hashCode() {
+        return hashBytes(buff, start, end - start);
+    }
+
+    public boolean hasLFLF() {
+        return hasLFLF(this);
+    }
+
+    public boolean hasRemaining() {
+        return start < end;
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     *
+     * @param s
+     *            the string
+     */
+//    public boolean startsWith(String s) {
+//        // Works only if enc==UTF
+//        byte[] b = buff;
+//        int blen = s.length();
+//        if (b == null || blen > end - start) {
+//            return false;
+//        }
+//        int boff = start;
+//        for (int i = 0; i < blen; i++) {
+//            if (b[boff++] != s.charAt(i)) {
+//                return false;
+//            }
+//        }
+//        return true;
+//    }
+
+    /* Returns true if the message bytes start with the specified byte array */
+//    public boolean startsWith(byte[] b2) {
+//        byte[] b1 = buff;
+//        if (b1 == null && b2 == null) {
+//            return true;
+//        }
+//
+//        int len = end - start;
+//        if (b1 == null || b2 == null || b2.length > len) {
+//            return false;
+//        }
+//        for (int i = start, j = 0; i < end && j < b2.length;) {
+//            if (b1[i++] != b2[j++])
+//                return false;
+//        }
+//        return true;
+//    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     *
+     * @param c
+     *            the character
+     * @param starting
+     *            The start position
+     */
+    public int indexOf(char c, int starting) {
+        int ret = indexOf(buff, start + starting, end, c);
+        return (ret >= start) ? ret - start : -1;
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     *
+     * @param s
+     *            the string
+     * @param pos
+     *            The position
+     */
+//    public boolean startsWithIgnoreCase(String s, int pos) {
+//        byte[] b = buff;
+//        int len = s.length();
+//        if (b == null || len + pos > end - start) {
+//            return false;
+//        }
+//        int off = start + pos;
+//        for (int i = 0; i < len; i++) {
+//            if (Ascii.toLower(b[off++]) != Ascii.toLower(s.charAt(i))) {
+//                return false;
+//            }
+//        }
+//        return true;
+//    }
+    public int indexOf(String src) {
+        return indexOf(src, 0, src.length(), 0);
+    }
+
+    public int indexOf(String src, int srcOff, int srcLen, int myOff) {
+        if ("".equals(src)) {
+            return myOff;
+        }
+        char first = src.charAt(srcOff);
+
+        // Look for first char
+        int srcEnd = srcOff + srcLen;
+
+        for (int i = myOff + start; i <= (end - srcLen); i++) {
+            if (buff[i] != first)
+                continue;
+            // found first char, now look for a match
+            int myPos = i + 1;
+            for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
+                if (buff[myPos++] != src.charAt(srcPos++))
+                    break;
+                if (srcPos == srcEnd)
+                    return i - start; // found it
+            }
+        }
+        return -1;
+    }
+
+    // hash ignoring case
+//    public int hashIgnoreCase() {
+//        return hashBytesIC(buff, start, end - start);
+//    }
+
+    public boolean isNull() {
+        return start == end;
+    }
+
+//    private static int hashBytesIC(byte bytes[], int start, int bytesLen) {
+//        int max = start + bytesLen;
+//        byte bb[] = bytes;
+//        int code = 0;
+//        for (int i = start; i < max; i++) {
+//            code = code * 37 + Ascii.toLower(bb[i]);
+//        }
+//        return code;
+//    }
+
+    @Override
+    public int limit() {
+        return end;
+    }
+
+    public void limit(int newEnd) {
+        end = newEnd;
+    }
+
+    /**
+     * Make space for len chars.
+     * If len is small, allocate a reserve space too.
+     */
+    public BBuffer makeSpace(int count) {
+        byte[] tmp = null;
+
+        int newSize;
+        int desiredSize = end + count;
+
+        if (buff == null) {
+            if (desiredSize < 16)
+                desiredSize = 16; // take a minimum
+            buff = new byte[desiredSize];
+            start = 0;
+            end = 0;
+            return this;
+        }
+
+        // limit < buf.length ( the buffer is already big )
+        // or we already have space XXX
+        if (desiredSize <= buff.length) {
+            return this;
+        }
+        // grow in larger chunks
+        if (desiredSize < 2 * buff.length) {
+            newSize = buff.length * 2;
+            tmp = new byte[newSize];
+        } else {
+            newSize = buff.length * 2 + count;
+            tmp = new byte[newSize];
+        }
+
+        System.arraycopy(buff, start, tmp, 0, end - start);
+        buff = tmp;
+        tmp = null;
+        end = end - start;
+        start = 0;
+        return this;
+    }
+
+//    /**
+//     * Find a character, no side effects.
+//     *
+//     * @return index of char if found, -1 if not
+//     */
+//    public static int findChars(byte buf[], int start, int end, byte c[]) {
+//        int clen = c.length;
+//        int offset = start;
+//        while (offset < end) {
+//            for (int i = 0; i < clen; i++)
+//                if (buf[offset] == c[i]) {
+//                    return offset;
+//                }
+//            offset++;
+//        }
+//        return -1;
+//    }
+
+//    /**
+//     * Find the first character != c
+//     *
+//     * @return index of char if found, -1 if not
+//     */
+//    public static int findNotChars(byte buf[], int start, int end, byte c[]) {
+//        int clen = c.length;
+//        int offset = start;
+//        boolean found;
+//
+//        while (offset < end) {
+//            found = true;
+//            for (int i = 0; i < clen; i++) {
+//                if (buf[offset] == c[i]) {
+//                    found = false;
+//                    break;
+//                }
+//            }
+//            if (found) { // buf[offset] != c[0..len]
+//                return offset;
+//            }
+//            offset++;
+//        }
+//        return -1;
+//    }
+
+    @Override
+    public int position() {
+        return start;
+    }
+
+    public void advance(int len) {
+        start += len;
+    }
+
+    @Override
+    public void position(int newStart) {
+        start = newStart;
+    }
+
+    public void put(byte b) {
+        makeSpace(1);
+        buff[end++] = b;
+    }
+
+    public void putByte(int b) {
+        makeSpace(1);
+        buff[end++] = (byte) b;
+    }
+
+    public int read(BBuffer res) {
+        res.setBytes(buff, start, remaining());
+        end = start;
+        return res.remaining();
+    }
+
+    /**
+     * Read a chunk from is.
+     *
+     * You don't need to use buffered input stream, we do the
+     * buffering.
+     */
+    public int read(InputStream is) throws IOException {
+        makeSpace(1024);
+        int res = is.read(buff, end, buff.length - end);
+        if (res > 0) {
+            end += res;
+        }
+        return res;
+    }
+
+    public int readAll(InputStream is) throws IOException {
+        int size = 0;
+        while (true) {
+            int res = read(is);
+            if (res < 0) {
+                return size;
+            }
+            size += res;
+        }
+    }
+
+    public int readByte() {
+        if (start == end) {
+            return -1;
+        }
+        return buff[start++];
+    }
+
+
+    /**
+     *  Read a line - excluding the line terminator, which is consummed as
+     *  well but not included in the response.
+     *
+     *  Line can end with CR, LF or CR/LF
+     *
+     * @param res
+     * @return number of bytes read, or -1 if line ending not found in buffer.
+     */
+    public int readLine(BBuffer res) {
+        int cstart = start;
+        while(start < end) {
+            byte chr = buff[start++];
+            if (chr == CR || chr == LF) {
+                res.setBytes(buff, cstart, start - cstart -1);
+                if (chr == CR) {
+                    if (start < end) {
+                        byte chr2 = buff[start];
+                        if (chr2 == LF) {
+                            start++;
+                        }
+                    }
+                }
+                return res.remaining();
+            }
+        }
+        start = cstart;
+        return -1;
+    }
+    /**
+     * Consume up to but not including delim.
+     *
+     */
+    public final int readToDelimOrSpace(byte delim,
+            BBuffer res) {
+        int resStart = start;
+        while (true) {
+            if (start >= end) {
+                break;
+            }
+            byte chr = buff[start];
+            if (chr == delim || chr == SP || chr == HT) {
+                break;
+            }
+            start++;
+        }
+        res.setBytes(buff, resStart, start - resStart);
+        return res.remaining();
+    }
+
+
+    /**
+     * Consume all up to the first space or \t, which will be the
+     * first character in the buffer.
+     *
+     * Consumed data is wrapped in res.
+     */
+    public int readToSpace(BBuffer res) {
+        int resStart = start;
+        while (true) {
+          if (start >= end) {
+              break;
+          }
+          if (buff[start] == SP
+                  || buff[start] == HT) {
+              break;
+          }
+          start++;
+        }
+        res.setBytes(buff, resStart, start - resStart);
+        return res.remaining();
+    }
+    /**
+     * Resets the message buff to an uninitialized state.
+     */
+    public void recycle() {
+        start = 0;
+        end = 0;
+    }
+    @Override
+    public void release() {
+//        synchronized (this) {
+//            useCount--;
+//            if (useCount == -1) {
+//                // all slices have been released -
+//                // TODO: callback, return to pool
+//            }
+//        }
+    }
+    public int remaining() {
+        return end - start;
+    }
+
+    public void reset() {
+        buff = null;
+    }
+
+    // -------------------- Setup --------------------
+    /**
+     * Sets the message bytes to the specified subarray of bytes.
+     *
+     * @param b
+     *            the ascii bytes
+     * @param off
+     *            the start offset of the bytes
+     * @param len
+     *            the length of the bytes
+     */
+    public void setBytes(byte[] b, int off, int len) {
+        throw new RuntimeException("Can't setBytes on allocated buffer");
+    }
+
+    public void wrap(BBucket b) {
+        setBytes(b.array(), b.position(), b.remaining());
+    }
+
+    public void wrap(ByteBuffer b) {
+        setBytes(b.array(), b.position(), b.remaining());
+    }
+
+    protected void setBytesInternal(byte[] b, int off, int len) {
+        buff = b;
+        start = off;
+        end = start + len;
+    }
+
+//    public final void lowerCase() {
+//        while (start < end) {
+//            byte chr = buff[start];
+//            if ((chr >= A) && (chr <= Z)) {
+//                buff[start] = (byte) (chr - LC_OFFSET);
+//            }
+//            start++;
+//        }
+//    }
+
+    public void setEnd(int i) {
+        end = i;
+    }
+
+    /**
+     * The old code from MessageBytes, used for setContentLength
+     * and setStatus.
+     * TODO: just use StringBuilder, the method is faster.
+     */
+    public void setLong(long l) {
+        if (array() == null) {
+            makeSpace(20);
+        }
+        long current = l;
+        byte[] buf = array();
+        int start = 0;
+        int end = 0;
+        if (l == 0) {
+            buf[end++] = (byte) '0';
+        } else if (l < 0) {
+            current = -l;
+            buf[end++] = (byte) '-';
+        }
+        while (current > 0) {
+            int digit = (int) (current % 10);
+            current = current / 10;
+            buf[end++] = Hex.HEX[digit];
+        }
+        setOffset(0);
+        setEnd(end);
+        // Inverting buffer
+        end--;
+        if (l < 0) {
+            start++;
+        }
+        while (end > start) {
+            byte temp = buf[start];
+            buf[start] = buf[end];
+            buf[end] = temp;
+            start++;
+            end--;
+        }
+    }
+
+    public void setOffset(int off) {
+        if (end < off)
+            end = off;
+        start = off;
+    }
+
+
+    public int skipEmptyLines() {
+        int resStart = start;
+        while (buff[start] == CR || buff[start] == LF) {
+            start++;
+            if (start == end) {
+                break;
+            }
+        }
+        return start - resStart;
+    }
+
+    public int skipSpace() {
+        int cstart = start;
+        while (true) {
+          if (start >= end) {
+            return start - cstart;
+          }
+          if ((buff[start] == SP) || (buff[start] == HT)) {
+            start++;
+          } else {
+            return start - cstart;
+          }
+        }
+    }
+
+    public int read() {
+        if (end  == start) {
+            return -1;
+        }
+        return (buff[start++] & 0xFF);
+
+    }
+
+    public int substract(BBuffer src) {
+
+        if (end == start) {
+            return -1;
+        }
+
+        int len = getLength();
+        src.append(buff, start, len);
+        start = end;
+        return len;
+
+    }
+
+    public int substract(byte src[], int off, int len)  {
+
+        if ((end - start) == 0) {
+            return -1;
+        }
+
+        int n = len;
+        if (len > getLength()) {
+            n = getLength();
+        }
+        System.arraycopy(buff, start, src, off, n);
+        start += n;
+        return n;
+
+    }
+
+    public String toString() {
+        return toString(DEFAULT_CHARACTER_ENCODING);
+    }
+
+    public String toString(String enc) {
+        if (null == buff) {
+            return null;
+        } else if (end == start) {
+            return "";
+        }
+
+        String strValue = null;
+        try {
+            if (enc == null) {
+                enc = DEFAULT_CHARACTER_ENCODING;
+            }
+
+            strValue = new String(buff, start, end - start, enc);
+            /*
+             * Does not improve the speed too much on most systems, it's safer
+             * to use the "clasical" new String().
+             *
+             * Most overhead is in creating char[] and copying, the internal
+             * implementation of new String() is very close to what we do. The
+             * decoder is nice for large buffers and if we don't go to String (
+             * so we can take advantage of reduced GC)
+             *
+             * // Method is commented out, in: return B2CConverter.decodeString(
+             * enc );
+             */
+        } catch (java.io.UnsupportedEncodingException e) {
+            // Use the platform encoding in that case; the usage of a bad
+            // encoding will have been logged elsewhere already
+            strValue = new String(buff, start, end - start);
+        }
+        return strValue;
+    }
+
+    public void wrapTo(BBuffer res) {
+        res.setBytes(buff, start, remaining());
+    }
+
+    /**
+     * Convert specified String to a byte array. This ONLY WORKS for ascii, UTF
+     * chars will be truncated.
+     *
+     * @param value
+     *            to convert to byte array
+     * @return the byte array value
+     */
+    public static final byte[] convertToBytes(String value) {
+        byte[] result = new byte[value.length()];
+        for (int i = 0; i < value.length(); i++) {
+            result[i] = (byte) value.charAt(i);
+        }
+        return result;
+    }
+
+    /**
+     * Find a character, no side effects.
+     *
+     * @return index of char if found, -1 if not
+     */
+    public static int findChar(byte buf[], int start, int end, char c) {
+        byte b = (byte) c;
+        int offset = start;
+        while (offset < end) {
+            if (buf[offset] == b) {
+                return offset;
+            }
+            offset++;
+        }
+        return -1;
+    }
+    private static int hashBytes(byte buff[], int start, int bytesLen) {
+        int max = start + bytesLen;
+        byte bb[] = buff;
+        int code = 0;
+        for (int i = start; i < max; i++) {
+            code = code * 31 + bb[i];
+            // TODO: if > 0x7F, convert to chars / switch to UTF8
+        }
+        return code;
+    }
+
+    public static boolean hasLFLF(BBucket bucket) {
+        int pos = bucket.position();
+        int lastValid = bucket.limit();
+        byte[] buf = bucket.array();
+
+        for (int i = pos; i < lastValid; i++) {
+            byte chr = buf[i];
+            if (chr == LF) {
+                if (i + 1 < lastValid && buf[i + 1] == CR) {
+                    // \n\r\n
+                    i++;
+                }
+                if (i + 1 < lastValid && buf[i + 1] == LF) {
+                    return true; // \n\n
+                }
+            } else if (chr == CR) {
+                if (i + 1 < lastValid && buf[i + 1] == CR) {
+                    return true; // \r\r
+                }
+                if (i + 1 < lastValid && buf[i + 1] == LF) {
+                        // \r\n
+                    i++; // skip LF
+                    if (i + 1 < lastValid && buf[i + 1] == CR &&
+                            i + 2 < lastValid && buf[i + 2] == LF) {
+                        i++;
+                        return true;
+                    }
+                }
+
+            }
+        }
+        return false;
+    }
+
+    public static int indexOf(byte bytes[], int off, int end, char qq) {
+        // Works only for UTF
+        while (off < end) {
+            byte b = bytes[off];
+            if (b == qq)
+                return off;
+            off++;
+        }
+        return -1;
+    }
+
+    /**
+     * Returns true if the specified ASCII character is a digit.
+     */
+
+    public static boolean isDigit(int c) {
+        return isDigit[c & 0xff];
+    }
+
+    /**
+     * Parses an unsigned integer from the specified subarray of bytes.
+     * @param b the bytes to parse
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     * @exception NumberFormatException if the integer format was invalid
+     */
+    public static int parseInt(byte[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        int n = c - '0';
+
+        while (--len > 0) {
+            if (!isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            n = n * 10 + c - '0';
+        }
+
+        return n;
+    }
+
+    /**
+     * Parses an unsigned long from the specified subarray of bytes.
+     * @param b the bytes to parse
+     * @param off the start offset of the bytes
+     * @param len the length of the bytes
+     * @exception NumberFormatException if the long format was invalid
+     */
+    public static long parseLong(byte[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        long n = c - '0';
+        long m;
+
+        while (--len > 0) {
+            if (!isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            m = n * 10 + c - '0';
+
+            if (m < n) {
+                // Overflow
+                throw new NumberFormatException();
+            } else {
+                n = m;
+            }
+        }
+
+        return n;
+    }
+
+
+
+    /**
+     * Returns the lower case equivalent of the specified ASCII character.
+     */
+    public static int toLower(int c) {
+        if (c > 0x7f) return c;
+        return toLower[c & 0xff] & 0xff;
+    }
+
+    /**
+     * Returns true if the specified ASCII character is upper case.
+     */
+
+    public static boolean isUpper(int c) {
+        return c < 0x7f && isUpper[c];
+    }
+
+    /**
+     * A slice of a bucket, holding reference to a parent bucket.
+     *
+     * This is used when a filter splits a bucket - the original
+     * will be replaced with 1 or more slices. When all slices are
+     * released, the parent will also be released.
+     *
+     * It is not possible to add data.
+     *
+     * @author Costin Manolache
+     */
+    static class IOBucketWrap extends BBuffer {
+        //IOBucket parent;
+
+
+        public BBuffer makeSpace(int count) {
+            throw new RuntimeException("Attempting to change buffer " +
+            		"on a wrapped BBuffer");
+        }
+
+        public void release() {
+//            if (parent != null) {
+//                parent.release();
+//            }
+        }
+
+        public void setBytes(byte[] b, int off, int len) {
+            super.setBytesInternal(b, off, len);
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BufferedIOReader.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BufferedIOReader.java
new file mode 100644
index 0000000..35d339a
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/BufferedIOReader.java
@@ -0,0 +1,380 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.io;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.CharBuffer;
+
+
+/**
+ * Cut&pasted from Harmony buffered reader ( apache license ).
+ * Changes:
+ * - additional method to recycle to avoid re-allocating on
+ * each request.
+ */
+public class BufferedIOReader extends BufferedReader {
+
+    // Not recycled - the buffer is tied to the message/IOReader
+    IOReader in;
+
+    private String enc;
+    boolean closed;
+    private char[] buf;
+    private int marklimit = -1;
+
+    private int count;
+
+    private int markpos = -1;
+
+    private int pos;
+
+    public BufferedIOReader(IOReader realReader) {
+        // we're not using super - we override all methods, but need the
+        // signature
+        super(DUMMY_READER, 1);
+        this.in = realReader;
+        buf = new char[8192];
+    }
+
+    public void recycle() {
+        enc = null;
+        closed = false;
+
+        if (in != null) {
+            in.recycle();
+        }
+        marklimit = -1;
+        count = 0;
+        markpos = -1;
+        pos = 0;
+    }
+
+    private void checkClosed() throws IOException {
+        if (closed) throw new IOException("closed");
+    }
+
+    public int read(CharBuffer target) throws IOException {
+        checkClosed();
+        int len = target.remaining();
+        int n = read(target.array(), target.position(), target.remaining());
+        if (n > 0)
+            target.position(target.position() + n);
+        return n;
+    }
+
+
+    public int read(char[] cbuf) throws IOException {
+        return read(cbuf, 0, cbuf.length);
+    }
+
+
+    /**
+     * Closes this reader. This implementation closes the buffered source reader
+     * and releases the buffer. Nothing is done if this reader has already been
+     * closed.
+     *
+     * @throws IOException
+     *             if an error occurs while closing this reader.
+     */
+    @Override
+    public void close() throws IOException {
+        synchronized (lock) {
+            if (!isClosed()) {
+                in.close();
+                closed = true;
+                // buf remains
+            }
+        }
+    }
+
+    private int fillbuf() throws IOException {
+        if (markpos == -1 || (pos - markpos >= marklimit)) {
+            /* Mark position not set or exceeded readlimit */
+            int result = in.read(buf, 0, buf.length);
+            if (result > 0) {
+                markpos = -1;
+                pos = 0;
+                count = result == -1 ? 0 : result;
+            }
+            return result;
+        }
+        if (markpos == 0 && marklimit > buf.length) {
+            /* Increase buffer size to accommodate the readlimit */
+            int newLength = buf.length * 2;
+            if (newLength > marklimit) {
+                newLength = marklimit;
+            }
+            char[] newbuf = new char[newLength];
+            System.arraycopy(buf, 0, newbuf, 0, buf.length);
+            buf = newbuf;
+        } else if (markpos > 0) {
+            System.arraycopy(buf, markpos, buf, 0, buf.length - markpos);
+        }
+
+        /* Set the new position and mark position */
+        pos -= markpos;
+        count = markpos = 0;
+        int charsread = in.read(buf, pos, buf.length - pos);
+        count = charsread == -1 ? pos : pos + charsread;
+        return charsread;
+    }
+
+    private boolean isClosed() {
+        return closed;
+    }
+
+    @Override
+    public void mark(int readlimit) throws IOException {
+        if (readlimit < 0) {
+            throw new IllegalArgumentException();
+        }
+        synchronized (lock) {
+            checkClosed();
+            marklimit = readlimit;
+            markpos = pos;
+        }
+    }
+
+    @Override
+    public boolean markSupported() {
+        return true;
+    }
+
+    @Override
+    public int read() throws IOException {
+        synchronized (lock) {
+            checkClosed();
+            /* Are there buffered characters available? */
+            if (pos < count || fillbuf() != -1) {
+                return buf[pos++];
+            }
+            markpos = -1;
+            return -1;
+        }
+    }
+
+    @Override
+    public int read(char[] buffer, int offset, int length) throws IOException {
+        synchronized (lock) {
+            checkClosed();
+            if (offset < 0 || offset > buffer.length - length || length < 0) {
+                throw new IndexOutOfBoundsException();
+            }
+            if (length == 0) {
+                return 0;
+            }
+            int required;
+            if (pos < count) {
+                /* There are bytes available in the buffer. */
+                int copylength = count - pos >= length ? length : count - pos;
+                System.arraycopy(buf, pos, buffer, offset, copylength);
+                pos += copylength;
+                if (copylength == length || !in.ready()) {
+                    return copylength;
+                }
+                offset += copylength;
+                required = length - copylength;
+            } else {
+                required = length;
+            }
+
+            while (true) {
+                int read;
+                /*
+                 * If we're not marked and the required size is greater than the
+                 * buffer, simply read the bytes directly bypassing the buffer.
+                 */
+                if (markpos == -1 && required >= buf.length) {
+                    read = in.read(buffer, offset, required);
+                    if (read == -1) {
+                        return required == length ? -1 : length - required;
+                    }
+                } else {
+                    if (fillbuf() == -1) {
+                        return required == length ? -1 : length - required;
+                    }
+                    read = count - pos >= required ? required : count - pos;
+                    System.arraycopy(buf, pos, buffer, offset, read);
+                    pos += read;
+                }
+                required -= read;
+                if (required == 0) {
+                    return length;
+                }
+                if (!in.ready()) {
+                    return length - required;
+                }
+                offset += read;
+            }
+        }
+    }
+
+    /**
+     * Returns the next line of text available from this reader. A line is
+     * represented by zero or more characters followed by {@code '\n'},
+     * {@code '\r'}, {@code "\r\n"} or the end of the reader. The string does
+     * not include the newline sequence.
+     *
+     * @return the contents of the line or {@code null} if no characters were
+     *         read before the end of the reader has been reached.
+     * @throws IOException
+     *             if this reader is closed or some other I/O error occurs.
+     */
+    public String readLine() throws IOException {
+        synchronized (lock) {
+            checkClosed();
+            /* Are there buffered characters available? */
+            if ((pos >= count) && (fillbuf() == -1)) {
+                return null;
+            }
+            for (int charPos = pos; charPos < count; charPos++) {
+                char ch = buf[charPos];
+                if (ch > '\r') {
+                    continue;
+                }
+                if (ch == '\n') {
+                    String res = new String(buf, pos, charPos - pos);
+                    pos = charPos + 1;
+                    return res;
+                } else if (ch == '\r') {
+                    String res = new String(buf, pos, charPos - pos);
+                    pos = charPos + 1;
+                    if (((pos < count) || (fillbuf() != -1))
+                            && (buf[pos] == '\n')) {
+                        pos++;
+                    }
+                    return res;
+                }
+            }
+
+            char eol = '\0';
+            StringBuilder result = new StringBuilder(80);
+            /* Typical Line Length */
+
+            result.append(buf, pos, count - pos);
+            pos = count;
+            while (true) {
+                /* Are there buffered characters available? */
+                if (pos >= count) {
+                    if (eol == '\n') {
+                        return result.toString();
+                    }
+                    // attempt to fill buffer
+                    if (fillbuf() == -1) {
+                        // characters or null.
+                        return result.length() > 0 || eol != '\0' ? result
+                                .toString() : null;
+                    }
+                }
+                for (int charPos = pos; charPos < count; charPos++) {
+                    if (eol == '\0') {
+                        if ((buf[charPos] == '\n' || buf[charPos] == '\r')) {
+                            eol = buf[charPos];
+                        }
+                    } else if (eol == '\r' && (buf[charPos] == '\n')) {
+                        if (charPos > pos) {
+                            result.append(buf, pos, charPos - pos - 1);
+                        }
+                        pos = charPos + 1;
+                        return result.toString();
+                    } else {
+                        if (charPos > pos) {
+                            result.append(buf, pos, charPos - pos - 1);
+                        }
+                        pos = charPos;
+                        return result.toString();
+                    }
+                }
+                if (eol == '\0') {
+                    result.append(buf, pos, count - pos);
+                } else {
+                    result.append(buf, pos, count - pos - 1);
+                }
+                pos = count;
+            }
+        }
+
+    }
+
+
+    @Override
+    public boolean ready() throws IOException {
+        synchronized (lock) {
+            checkClosed();
+            return ((count - pos) > 0) || in.ready();
+        }
+    }
+
+    @Override
+    public void reset() throws IOException {
+        synchronized (lock) {
+            checkClosed();
+            if (markpos == -1) {
+                throw new IOException("No mark");
+            }
+            pos = markpos;
+        }
+    }
+
+    @Override
+    public long skip(long amount) throws IOException {
+        if (amount < 0) {
+            throw new IllegalArgumentException();
+        }
+        synchronized (lock) {
+            checkClosed();
+            if (amount < 1) {
+                return 0;
+            }
+            if (count - pos >= amount) {
+                pos += amount;
+                return amount;
+            }
+
+            long read = count - pos;
+            pos = count;
+            while (read < amount) {
+                if (fillbuf() == -1) {
+                    return read;
+                }
+                if (count - pos >= amount - read) {
+                    pos += amount - read;
+                    return amount;
+                }
+                // Couldn't get all the characters, skip what we read
+                read += (count - pos);
+                pos = count;
+            }
+            return amount;
+        }
+    }
+
+    private static Reader DUMMY_READER = new Reader() {
+        @Override
+        public void close() throws IOException {
+        }
+
+        @Override
+        public int read(char[] cbuf, int off, int len) throws IOException {
+            return 0;
+        }
+    };
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBucket.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBucket.java
new file mode 100644
index 0000000..688785e
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBucket.java
@@ -0,0 +1,508 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.Serializable;
+import java.nio.CharBuffer;
+
+/**
+ * Wraps a char[].
+ *
+ * Doesn't provide any mutation methods. Classes in this package
+ * have access to the buffer, for conversions.
+ *
+ *
+ * @author Costin Manolache
+ */
+public class CBucket implements CharSequence, Comparable, Serializable {
+    protected char value[];
+
+    protected int start;
+
+    protected int end;
+
+    // Reused.
+    protected CharBuffer cb;
+
+    // cache
+    protected String strValue;
+    protected int hash;
+
+    public CBucket() {
+    }
+
+    /**
+     * Used by IOWriter for conversion. Will not modify the content.
+     */
+    CharBuffer getNioBuffer() {
+        if (cb == null || cb.array() != value) {
+            cb = CharBuffer.wrap(value, start, end - start);
+        } else {
+            cb.position(start);
+            cb.limit(end);
+        }
+        return cb;
+    }
+
+    public void recycle() {
+        start = 0;
+        end = 0;
+        value = null;
+        strValue = null;
+        hash = 0;
+    }
+
+    public String toString() {
+        if (null == value) {
+            return null;
+        } else if (end - start == 0) {
+            return "";
+        }
+        if (strValue == null) {
+            strValue = new String(value, start, end - start);
+        }
+        return strValue;
+    }
+
+    /**
+     * Same as String
+     */
+    public int hashCode() {
+        int h = hash;
+        if (h == 0) {
+            int off = start;
+            char val[] = value;
+
+            for (int i = start; i < end; i++) {
+                h = 31*h + val[off++];
+            }
+            hash = h;
+        }
+        return h;
+    }
+
+    public long getLong() {
+        return parseLong(value, start, end - start);
+    }
+
+    public int getInt() {
+        return parseInt(value, start, end - start);
+    }
+
+    public static int parseInt(char[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !BBuffer.isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        int n = c - '0';
+
+        while (--len > 0) {
+            if (!BBuffer.isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            n = n * 10 + c - '0';
+        }
+
+        return n;
+    }
+
+
+    public static long parseLong(char[] b, int off, int len)
+        throws NumberFormatException
+    {
+        int c;
+
+        if (b == null || len <= 0 || !BBuffer.isDigit(c = b[off++])) {
+            throw new NumberFormatException();
+        }
+
+        long n = c - '0';
+        long m;
+
+        while (--len > 0) {
+            if (!BBuffer.isDigit(c = b[off++])) {
+                throw new NumberFormatException();
+            }
+            m = n * 10 + c - '0';
+
+            if (m < n) {
+                // Overflow
+                throw new NumberFormatException();
+            } else {
+                n = m;
+            }
+        }
+
+        return n;
+    }
+
+
+    /**
+     * Compares the message bytes to the specified String object.
+     *
+     * @param s
+     *            the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equals(String s) {
+        char[] c = value;
+        int len = end - start;
+        if (c == null || len != s.length()) {
+            return false;
+        }
+        int off = start;
+        for (int i = 0; i < len; i++) {
+            if (c[off++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Compares the message bytes to the specified String object.
+     *
+     * @param s
+     *            the String to compare
+     * @return true if the comparison succeeded, false otherwise
+     */
+    public boolean equalsIgnoreCase(String s) {
+        char[] c = value;
+        int len = end - start;
+        if (c == null || len != s.length()) {
+            return false;
+        }
+        int off = start;
+        for (int i = 0; i < len; i++) {
+            if (BBuffer.toLower(c[off++]) != BBuffer.toLower(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean equals(Object obj) {
+        if (obj instanceof CBuffer) {
+            CBuffer cc = (CBuffer) obj;
+            return equals(cc.value, cc.start, cc.length());
+        } else if (obj instanceof String) {
+            return equals((String)obj);
+        }
+        return false;
+    }
+
+    public boolean equals(char b2[], int off2, int len2) {
+        char b1[] = value;
+        if (b1 == null && b2 == null)
+            return true;
+
+        if (b1 == null || b2 == null || end - start != len2) {
+            return false;
+        }
+        int off1 = start;
+        int len = end - start;
+        while (len-- > 0) {
+            if (b1[off1++] != b2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public boolean equals(byte b2[], int off2, int len2) {
+        char b1[] = value;
+        if (b2 == null && b1 == null)
+            return true;
+
+        if (b1 == null || b2 == null || end - start != len2) {
+            return false;
+        }
+        int off1 = start;
+        int len = end - start;
+
+        while (len-- > 0) {
+            if (b1[off1++] != (char) b2[off2++]) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     *
+     * @param s
+     *            the string
+     */
+    public boolean startsWith(String s) {
+        char[] c = value;
+        int len = s.length();
+        if (c == null || len > end - start) {
+            return false;
+        }
+        int off = start;
+        for (int i = 0; i < len; i++) {
+            if (c[off++] != s.charAt(i)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     *
+     * @param s
+     *            the string
+     */
+    public boolean startsWithIgnoreCase(String s, int pos) {
+        char[] c = value;
+        int len = s.length();
+        if (c == null || len + pos > end - start) {
+            return false;
+        }
+        int off = start + pos;
+        for (int i = 0; i < len; i++) {
+            if (BBuffer.toLower(c[off++]) != BBuffer.toLower(s.charAt(i))) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    public int indexOf(char c) {
+        return indexOf(c, start);
+    }
+
+    public int lastIndexOf(char c) {
+        return lastIndexOf(c, 0, end - start);
+    }
+
+    /**
+     */
+    public int lastIndexOf(char c, int off, int len) {
+        char[] buf = value;
+        int slash = -1;
+        for (int i = start + len - 1; i >= start + off; i--) {
+            if (buf[i] == c) {
+                slash = i - start;
+                break;
+            }
+        }
+        return slash;
+    }
+
+    /**
+     * Returns true if the message bytes starts with the specified string.
+     *
+     * @param c
+     *            the character
+     */
+    public int indexOf(char c, int starting) {
+        int ret = indexOf(value, start + starting, end, c);
+        return (ret >= start) ? ret - start : -1;
+    }
+
+    public static int indexOf(char chars[], int off, int cend, char qq) {
+        while (off < cend) {
+            char b = chars[off];
+            if (b == qq)
+                return off;
+            off++;
+        }
+        return -1;
+    }
+
+    public int indexOf(String src) {
+        return indexOf(src, 0, src.length(), 0);
+    }
+
+    public int indexOf(String src, int srcOff, int srcLen, int myOff) {
+        char first = src.charAt(srcOff);
+
+        // Look for first char
+        int srcEnd = srcOff + srcLen;
+
+        for (int i = myOff + start; i <= (end - srcLen); i++) {
+            if (value[i] != first)
+                continue;
+            // found first char, now look for a match
+            int myPos = i + 1;
+            for (int srcPos = srcOff + 1; srcPos < srcEnd;) {
+                if (value[myPos++] != src.charAt(srcPos++))
+                    break;
+                if (srcPos == srcEnd)
+                    return i - start; // found it
+            }
+        }
+        return -1;
+    }
+
+    public char lastChar() {
+        return value[end - 1];
+    }
+
+    public char charAt(int index) {
+        return value[index + start];
+    }
+
+    public void wrap(char[] buff, int start, int end) {
+        if (value != null) {
+            throw new RuntimeException("Can wrap only once");
+        }
+        this.value = buff;
+        this.start = start;
+        this.end = end;
+    }
+
+    public CharSequence subSequence(int sstart, int send) {
+        CBucket seq = new CBucket();
+        seq.wrap(this.value, start + sstart, start + send);
+        return seq;
+    }
+
+    public int length() {
+        return end - start;
+    }
+
+    @Override
+    public int compareTo(Object o) {
+        // Code based on Harmony
+        if (o instanceof CBuffer) {
+            CBuffer dest = (CBuffer) o;
+            int o1 = start, o2 = dest.start, result;
+            int len = end - start;
+            int destLen = dest.end - dest.start;
+            int fin = (len < destLen ?
+                    end : start + destLen);
+            char[] target = dest.value;
+            while (o1 < fin) {
+                if ((result = value[o1++] - target[o2++]) != 0) {
+                    return result;
+                }
+            }
+            return len - destLen;
+
+        } else if (o instanceof CharSequence) {
+            CharSequence dest = (CharSequence) o;
+            int o1 = start, o2 = 0, result;
+            int len = end - start;
+            int destLen = dest.length();
+            int fin = (len < destLen ?
+                    end : start + destLen);
+            while (o1 < fin) {
+                if ((result = value[o1++] - dest.charAt(o2++)) != 0) {
+                    return result;
+                }
+            }
+            return len - destLen;
+
+        } else {
+            throw new RuntimeException("CompareTo not supported " + o);
+        }
+    }
+
+    /**
+     * Compare given char chunk with String ignoring case.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    public final int compareIgnoreCase(String compareTo) {
+        int result = 0;
+        char[] c = value;
+        int len = compareTo.length();
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (BBuffer.toLower(c[i + start]) > BBuffer.toLower(compareTo.charAt(i))) {
+                result = 1;
+            } else if (BBuffer.toLower(c[i + start]) < BBuffer.toLower(compareTo.charAt(i))) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length() > (end - start)) {
+                result = -1;
+            } else if (compareTo.length() < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Compare given char chunk with String.
+     * Return -1, 0 or +1 if inferior, equal, or superior to the String.
+     */
+    public final int compare(String compareTo) {
+        int result = 0;
+        char[] c = value;
+        int len = compareTo.length();
+        if ((end - start) < len) {
+            len = end - start;
+        }
+        for (int i = 0; (i < len) && (result == 0); i++) {
+            if (c[i + start] > compareTo.charAt(i)) {
+                result = 1;
+            } else if (c[i + start] < compareTo.charAt(i)) {
+                result = -1;
+            }
+        }
+        if (result == 0) {
+            if (compareTo.length() > (end - start)) {
+                result = -1;
+            } else if (compareTo.length() < (end - start)) {
+                result = 1;
+            }
+        }
+        return result;
+    }
+
+    public int getExtension(CBuffer ext, char slashC, char dotC) {
+        int slash = lastIndexOf(slashC);
+        if (slash < 0) {
+            slash = 0;
+        }
+        int dot = lastIndexOf(dotC, slash, length());
+        if (dot < 0) {
+            return -1;
+        }
+        ext.wrap(this, dot + 1, length());
+        return dot;
+    }
+
+    /**
+     * Find the position of the nth slash, in the given char chunk.
+     */
+    public final int nthSlash(int n) {
+        char[] c = value;
+        int pos = start;
+        int count = 0;
+
+        while (pos < end) {
+            if ((c[pos++] == '/') && ((++count) == n)) {
+                pos--;
+                break;
+            }
+        }
+
+        return pos - start;
+    }
+
+
+    public boolean hasUpper() {
+        for (int i = start; i < end; i++) {
+            char c = value[i];
+            if (c < 0x7F && BBuffer.isUpper(c)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBuffer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBuffer.java
new file mode 100644
index 0000000..86b4234
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/CBuffer.java
@@ -0,0 +1,387 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.io;
+
+import java.io.IOException;
+import java.nio.CharBuffer;
+
+
+/**
+ * Similar with StringBuilder or StringBuffer, but with access to the
+ * raw buffer - this avoids copying the data.
+ *
+ * Utilities to manipluate char chunks. While String is the easiest way to
+ * manipulate chars ( search, substrings, etc), it is known to not be the most
+ * efficient solution - Strings are designed as imutable and secure objects.
+ *
+ * @author dac@sun.com
+ * @author James Todd [gonzo@sun.com]
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ */
+public class CBuffer extends CBucket implements Cloneable,
+        Appendable {
+
+
+    /**
+     * Creates a new, uninitialized CharChunk object.
+     */
+    public static CBuffer newInstance() {
+        return new CBuffer();
+    }
+
+    private CBuffer() {
+    }
+
+    /**
+     * Resets the message bytes to an uninitialized state.
+     */
+    public void recycle() {
+        dirty();
+        start = 0;
+        end = 0;
+    }
+
+    /**
+     * Same as String
+     */
+    public int hashCode() {
+        int h = 0;
+        int off = start;
+        char val[] = value;
+
+        for (int i = start; i < end; i++) {
+            h = 31*h + val[off++];
+        }
+        return h;
+    }
+
+    public String toString() {
+        if (null == value) {
+            return null;
+        } else if (end - start == 0) {
+            return "";
+        }
+        return new String(value, start, end - start);
+    }
+
+    public void wrap(char[] buff, int start, int end) {
+        dirty();
+        this.value = buff;
+        this.start = start;
+        this.end = end;
+    }
+
+    public void wrap(CBucket buff, int off, int srcEnd) {
+        dirty();
+        this.value = buff.value;
+        this.start = buff.start + off;
+        this.end = this.start + srcEnd - off;
+    }
+
+
+    // ----------- Used for IOWriter / conversion ---------
+
+    public char[] array() {
+        return value;
+    }
+
+    public int position() {
+        return start;
+    }
+
+    CharBuffer getAppendCharBuffer() {
+        makeSpace(16);
+        if (cb == null || cb.array() != value) {
+            cb = CharBuffer.wrap(value, end, value.length - end);
+        } else {
+            cb.position(end);
+            cb.limit(value.length);
+        }
+        return cb;
+    }
+
+    void returnNioBuffer(CharBuffer c) {
+        dirty();
+        start = c.position();
+    }
+
+    void returnAppendCharBuffer(CharBuffer c) {
+        dirty();
+        end = c.position();
+    }
+
+    // -------- Delete / replace ---------------
+
+    /**
+     * 'Delete' all chars after offset.
+     *
+     * @param offset
+     */
+    public void delete(int offset) {
+       dirty();
+       end = start + offset;
+    }
+
+    // -------------------- Adding data --------------------
+
+    /**
+     * Append methods take start and end - similar with this one.
+     * The source is not modified.
+     */
+    @Override
+    public CBuffer append(CharSequence csq, int astart, int aend)
+            throws IOException {
+        makeSpace(aend - astart);
+
+        for (int i = astart; i < aend; i++) {
+            value[end++] = csq.charAt(i);
+        }
+        return this;
+    }
+
+    public CBuffer append(char b) {
+        makeSpace(1);
+        value[end++] = b;
+        return this;
+    }
+
+    public CBuffer append(int i) {
+        // TODO: can be optimizeed...
+        append(Integer.toString(i));
+        return this;
+    }
+
+    /**
+     * Add data to the buffer
+     */
+    public CBuffer append(char src[], int srcStart, int srcEnd)  {
+        int len = srcEnd - srcStart;
+        if (len == 0) {
+            return this;
+        }
+        // will grow, up to limit
+        makeSpace(len);
+
+        // assert: makeSpace made enough space
+        System.arraycopy(src, srcStart, value, end, len);
+        end += len;
+        return this;
+    }
+
+    /**
+     * Add data to the buffer
+     */
+    public CBuffer append(StringBuffer sb) {
+        int len = sb.length();
+        if (len == 0) {
+            return this;
+        }
+        makeSpace(len);
+        sb.getChars(0, len, value, end);
+        end += len;
+        return this;
+    }
+
+    /**
+     * Append a string to the buffer
+     */
+    public CBuffer append(String s) {
+        if (s == null || s.length() == 0) {
+            return this;
+        }
+        append(s, 0, s.length());
+        return this;
+    }
+
+
+    /**
+     * Append a string to the buffer
+     */
+    public CBuffer append(String s, int off, int srcEnd) {
+        if (s == null)
+            return this;
+
+        // will grow, up to limit
+        makeSpace(srcEnd - off);
+
+        // assert: makeSpace made enough space
+        s.getChars(off, srcEnd, value, end);
+        end += srcEnd - off;
+        return this;
+    }
+
+    // TODO: long, int conversions -> get from harmony Long
+    public CBuffer appendInt(int i) {
+        // TODO: copy from harmony StringBuffer
+        append(Integer.toString(i));
+        return this;
+    }
+
+
+    public Appendable append(CharSequence cs) {
+        if (cs instanceof CBuffer) {
+            CBuffer src = (CBuffer) cs;
+            append(src.value, src.start, src.end);
+        } else if (cs instanceof String) {
+            append((String) cs);
+        } else {
+            for (int i = 0; i < cs.length(); i++) {
+                append(cs.charAt(i));
+            }
+        }
+        return  this;
+    }
+
+    public CBuffer append(CBuffer src) {
+        append(src.value, src.start, src.end);
+        return  this;
+    }
+
+
+    public CBuffer append(BBucket bb) {
+        byte[] bbuf = bb.array();
+        int start = bb.position();
+        appendAscii(bbuf, start, bb.remaining());
+        return this;
+    }
+
+    public CBuffer appendAscii(byte[] bbuf, int start, int len) {
+        makeSpace(len);
+        char[] cbuf = value;
+        for (int i = 0; i < len; i++) {
+            cbuf[end + i] = (char) (bbuf[i + start] & 0xff);
+        }
+        end += len;
+        return this;
+    }
+
+
+    public void toAscii(BBuffer bb) {
+        for (int i = start; i < end; i++) {
+            bb.append(value[i]);
+        }
+    }
+
+    /**
+     *  Append and advance CharBuffer.
+     *
+     * @param c
+     */
+    public CBuffer put(CharBuffer c) {
+        append(c.array(), c.position(), c.limit());
+        c.position(c.limit());
+        return this;
+    }
+
+    // ------------- 'set' methods ---------------
+    // equivalent with clean + append
+
+    public CBuffer set(CBuffer csq, int off, int len) {
+        recycle();
+        append(csq.value, csq.start + off, csq.start + off + len);
+        return this;
+    }
+
+    public CBuffer setChars(char[] c, int off, int len) {
+        recycle();
+        append(c, off, off + len);
+        return this;
+    }
+
+    public CBuffer set(BBucket bb) {
+        recycle();
+        byte[] bbuf = bb.array();
+        int start = bb.position();
+        appendAscii(bbuf, start, bb.remaining());
+        return this;
+    }
+
+    public CBuffer set(CharSequence csq) {
+        recycle();
+        append(csq);
+        return this;
+    }
+
+    public CBuffer set(CBuffer csq) {
+        recycle();
+        append(csq);
+        return this;
+    }
+
+    public CBuffer set(String csq) {
+        recycle();
+        append(csq);
+        return this;
+    }
+
+    private void dirty() {
+        hash = 0;
+        strValue = null;
+    }
+
+    /**
+     * Make space for len chars. If len is small, allocate a reserve space too.
+     * Never grow bigger than limit.
+     */
+    private void makeSpace(int count) {
+        dirty();
+        char[] tmp = null;
+
+        int newSize;
+        int desiredSize = end + count;
+
+        if (value == null) {
+            if (desiredSize < 256)
+                desiredSize = 256; // take a minimum
+            value = new char[desiredSize];
+        }
+
+        // limit < buf.length ( the buffer is already big )
+        // or we already have space XXX
+        if (desiredSize <= value.length) {
+            return;
+        }
+        // grow in larger chunks
+        if (desiredSize < 2 * value.length) {
+            newSize = value.length * 2;
+            tmp = new char[newSize];
+        } else {
+            newSize = value.length * 2 + count;
+            tmp = new char[newSize];
+        }
+
+        System.arraycopy(value, 0, tmp, 0, end);
+        value = tmp;
+        tmp = null;
+    }
+
+    public void toLower() {
+        for (int i = start; i < end; i++) {
+            char c = value[i];
+            if (c < 0x7F) {
+                if (BBuffer.isUpper(c)) {
+                    value[i] = (char) BBuffer.toLower(c);
+                }
+
+            }
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java
new file mode 100644
index 0000000..31529fe
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/DumpChannel.java
@@ -0,0 +1,120 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+// TODO: dump to a file, hex, etc.
+
+/**
+ * For debug - will print all bytes that go trough the channel
+ */
+public class DumpChannel extends IOChannel {
+
+    IOBuffer in = new IOBuffer(this);
+    IOBuffer out = new IOBuffer(this);
+    static final boolean dumpToFile = false;
+    static int idCnt = 0;
+
+    DumpChannel(String id) {
+        this.id = id + idCnt++;
+    }
+
+    public static IOChannel wrap(String id, IOChannel net) throws IOException {
+        if (id == null) {
+            id = "";
+        }
+        DumpChannel dmp = new DumpChannel(id + idCnt++);
+        net.setHead(dmp);
+        return dmp;
+    }
+
+    public String toString() {
+        return "Dump-" + id + "-" + net.toString();
+    }
+
+    @Override
+    public void handleReceived(IOChannel ch) throws IOException {
+        processInput(ch.getIn());
+    }
+
+    private void processInput(IOBuffer netIn) throws IOException {
+        boolean any = false;
+        while (true) {
+            BBucket first = netIn.popFirst();
+            if (first == null) {
+                if (netIn.isClosedAndEmpty()) {
+                    out("IN", first, true);
+                    in.close();
+                    any = true;
+                }
+                if (any) {
+                    sendHandleReceivedCallback();
+                }
+                return;
+            }
+            any = true;
+            out("IN", first, false);
+            if (!in.isAppendClosed()) {
+                in.queue(first);
+            }
+        }
+    }
+
+    public void startSending() throws IOException {
+        while (true) {
+            BBucket first = out.popFirst();
+            if (first == null) {
+                if (out.isClosedAndEmpty()) {
+                    out("OUT", first, true);
+                    net.getOut().close();
+                }
+
+                net.startSending();
+                return;
+            }
+            // Dump
+            out("OUT", first, net.getOut().isAppendClosed());
+            net.getOut().queue(first);
+        }
+    }
+
+    static int did = 0;
+
+    protected void out(String dir, BBucket first, boolean closed) {
+        // Dump
+        if (first != null) {
+            String hd = Hex.getHexDump(first.array(), first.position(),
+                    first.remaining(), true);
+            System.err.println("\n" + dir + ": " + id + " " +
+                    (closed ? "CLS" : "") +
+                    + first.remaining() + "\n" +
+                    hd);
+        } else {
+            System.err.println("\n" + dir + ": " + id + " " +
+                    (closed ? "CLS " : "") +
+                     "END\n");
+        }
+        if (dumpToFile && first != null) {
+            try {
+                OutputStream os = new FileOutputStream("dmp" + did++);
+                os.write(first.array(), first.position(), first.remaining());
+                os.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+    }
+
+    @Override
+    public IOBuffer getIn() {
+        return in;
+    }
+
+    @Override
+    public IOBuffer getOut() {
+        return out;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FastHttpDateFormat.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FastHttpDateFormat.java
new file mode 100644
index 0000000..f74b9b5
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FastHttpDateFormat.java
@@ -0,0 +1,231 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.io;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Utility class to generate HTTP dates.
+ *
+ * @author Remy Maucherat
+ */
+public final class FastHttpDateFormat {
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    protected static final int CACHE_SIZE =
+        Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000"));
+
+
+    /**
+     * HTTP date format.
+     */
+    protected static final SimpleDateFormat format =
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     */
+    protected static final SimpleDateFormat formats[] = {
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+    };
+
+
+    protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+
+        format.setTimeZone(gmtZone);
+
+        formats[0].setTimeZone(gmtZone);
+        formats[1].setTimeZone(gmtZone);
+        formats[2].setTimeZone(gmtZone);
+
+    }
+
+
+    /**
+     * Instant on which the currentDate object was generated.
+     */
+    protected static long currentDateGenerated = 0L;
+
+
+    /**
+     * Current formatted date.
+     */
+    protected static String currentDate = null;
+
+
+    /**
+     * Formatter cache.
+     */
+    protected static final ConcurrentHashMap<Long, String> formatCache =
+        new ConcurrentHashMap<Long, String>(CACHE_SIZE);
+
+
+    /**
+     * Parser cache.
+     */
+    protected static final ConcurrentHashMap<String, Long> parseCache =
+        new ConcurrentHashMap<String, Long>(CACHE_SIZE);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Get the current date in HTTP format.
+     */
+    public static final String getCurrentDate() {
+
+        long now = System.currentTimeMillis();
+        if ((now - currentDateGenerated) > 1000) {
+            synchronized (format) {
+                if ((now - currentDateGenerated) > 1000) {
+                    currentDateGenerated = now;
+                    currentDate = format.format(new Date(now));
+                }
+            }
+        }
+        return currentDate;
+
+    }
+
+
+    /**
+     * Get the HTTP format of the specified date.
+     */
+    public static final String formatDate
+        (long value, DateFormat threadLocalformat) {
+
+        Long longValue = new Long(value);
+        String cachedDate = formatCache.get(longValue);
+        if (cachedDate != null)
+            return cachedDate;
+
+        String newDate = null;
+        Date dateValue = new Date(value);
+        if (threadLocalformat != null) {
+            newDate = threadLocalformat.format(dateValue);
+            updateFormatCache(longValue, newDate);
+        } else {
+            synchronized (formatCache) {
+                synchronized (format) {
+                    newDate = format.format(dateValue);
+                }
+                updateFormatCache(longValue, newDate);
+            }
+        }
+        return newDate;
+
+    }
+
+
+    /**
+     * Try to parse the given date as a HTTP date.
+     */
+    public static final long parseDate(String value,
+                                       DateFormat[] threadLocalformats) {
+
+        Long cachedDate = parseCache.get(value);
+        if (cachedDate != null)
+            return cachedDate.longValue();
+
+        Long date = null;
+        if (threadLocalformats != null) {
+            date = internalParseDate(value, threadLocalformats);
+            updateParseCache(value, date);
+        } else {
+            synchronized (parseCache) {
+                date = internalParseDate(value, formats);
+                updateParseCache(value, date);
+            }
+        }
+        if (date == null) {
+            return (-1L);
+        } else {
+            return date.longValue();
+        }
+
+    }
+
+
+    /**
+     * Parse date with given formatters.
+     */
+    private static final Long internalParseDate
+        (String value, DateFormat[] formats) {
+        Date date = null;
+        for (int i = 0; (date == null) && (i < formats.length); i++) {
+            try {
+                date = formats[i].parse(value);
+            } catch (ParseException e) {
+                ;
+            }
+        }
+        if (date == null) {
+            return null;
+        }
+        return new Long(date.getTime());
+    }
+
+
+    /**
+     * Update cache.
+     */
+    private static void updateFormatCache(Long key, String value) {
+        if (value == null) {
+            return;
+        }
+        if (formatCache.size() > CACHE_SIZE) {
+            formatCache.clear();
+        }
+        formatCache.put(key, value);
+    }
+
+
+    /**
+     * Update cache.
+     */
+    private static void updateParseCache(String key, Long value) {
+        if (value == null) {
+            return;
+        }
+        if (parseCache.size() > CACHE_SIZE) {
+            parseCache.clear();
+        }
+        parseCache.put(key, value);
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnector.java
new file mode 100644
index 0000000..f4dd93b
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnector.java
@@ -0,0 +1,27 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+
+/**
+ * Initial abstraction for non-blocking File access and to
+ * support other abstraction.
+ *
+ * Tomcat uses JNDI - but that's blocking, does lots of data copy,
+ * is complex.
+ *
+ * Work in progress..
+ */
+public abstract class FileConnector extends IOConnector {
+
+    public static class FileInfo {
+        String type;
+        int mode;
+        long size;
+
+    }
+
+    public abstract boolean isDirectory(String path);
+
+    public abstract boolean isFile(String path);
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java
new file mode 100644
index 0000000..082c025
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FileConnectorJavaIo.java
@@ -0,0 +1,49 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.File;
+import java.io.IOException;
+
+
+/**
+ * Catalina uses JNDI to abstract filesystem - this is both heavy and
+ * a bit complex.
+ *
+ * This is also a bit complex - but hopefully we can implement it as
+ * non-blocking and without much copy.
+ *
+ */
+public class FileConnectorJavaIo extends FileConnector {
+    File base;
+
+    public FileConnectorJavaIo(File file) {
+        this.base = file;
+    }
+
+    @Override
+    public boolean isDirectory(String path) {
+        File file = new File(base, path);
+        return file.isDirectory();
+    }
+
+    @Override
+    public boolean isFile(String path) {
+        File file = new File(base, path);
+        return file.exists() && !file.isDirectory();
+    }
+
+    @Override
+    public void acceptor(ConnectedCallback sc,
+            CharSequence port,
+            Object extra) throws IOException {
+        // TODO: unix domain socket impl.
+        // Maybe: detect new files in the filesystem ?
+    }
+
+    @Override
+    public void connect(String host, int port, ConnectedCallback sc)
+            throws IOException {
+    }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FutureCallbacks.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FutureCallbacks.java
new file mode 100644
index 0000000..782228c
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/FutureCallbacks.java
@@ -0,0 +1,171 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.locks.AbstractQueuedSynchronizer;
+
+
+
+/**
+ * Support for blocking calls and callbacks.
+ *
+ * Unlike FutureTask, it is possible to reuse this and hopefully
+ * easier to extends. Also has callbacks.
+ *
+ * @author Costin Manolache
+ */
+public class FutureCallbacks<V> implements Future<V> {
+
+    // Other options: ReentrantLock uses AbstractQueueSynchronizer,
+    // more complex. Same for CountDownLatch
+    // FutureTask - uses Sync as well, ugly interface with
+    // Callable, can't be recycled.
+    // Mina: simple object lock, doesn't extend java.util.concurent.Future
+
+    private Sync sync = new Sync();
+
+    private V value;
+
+    public static interface Callback<V> {
+        public void run(V param);
+    }
+
+    private List<Callback<V>> callbacks = new ArrayList();
+
+    public FutureCallbacks() {
+    }
+
+    /**
+     * Unlocks the object if it was locked. Should be called
+     * when the object is reused.
+     *
+     * Callbacks will not be invoked.
+     */
+    public void reset() {
+        sync.releaseShared(0);
+        sync.reset();
+    }
+
+    public void recycle() {
+        callbacks.clear();
+        sync.releaseShared(0);
+        sync.reset();
+    }
+
+    /**
+     * Unlocks object and calls the callbacks.
+     * @param v
+     *
+     * @throws IOException
+     */
+    public void signal(V v) throws IOException {
+        sync.releaseShared(0);
+        onSignal(v);
+    }
+
+    protected boolean isSignaled() {
+        return true;
+    }
+
+    /**
+     * Override to call specific callbacks
+     */
+    protected void onSignal(V v) {
+        for (Callback<V> cb: callbacks) {
+            if (cb != null) {
+                cb.run(v);
+            }
+        }
+    }
+
+    /**
+     * Set the response. Will cause the callback to be called and lock to be
+     * released.
+     *
+     * @param value
+     * @throws IOException
+     */
+    public void setValue(V value) throws IOException {
+        synchronized (this) {
+            this.value = value;
+            signal(value);
+        }
+    }
+
+    public void waitSignal(long to) throws IOException {
+        try {
+            get(to, TimeUnit.MILLISECONDS);
+        } catch (InterruptedException e1) {
+            throw new WrappedException(e1);
+        } catch (TimeoutException e1) {
+            throw new WrappedException(e1);
+        } catch (ExecutionException e) {
+            throw new WrappedException(e);
+        }
+    }
+
+    @Override
+    public V get() throws InterruptedException, ExecutionException {
+        sync.acquireSharedInterruptibly(0);
+        return value;
+    }
+
+    @Override
+    public V get(long timeout, TimeUnit unit) throws InterruptedException,
+            ExecutionException, TimeoutException {
+        if (!sync.tryAcquireSharedNanos(0, unit.toNanos(timeout))) {
+            throw new TimeoutException("Waiting " + timeout);
+        }
+        return value;
+    }
+
+
+    @Override
+    public boolean cancel(boolean mayInterruptIfRunning) {
+        return false;
+    }
+
+    @Override
+    public boolean isCancelled() {
+        return false;
+    }
+
+    @Override
+    public boolean isDone() {
+        return sync.isSignaled();
+    }
+
+    private class Sync extends AbstractQueuedSynchronizer {
+
+        static final int DONE = 1;
+        static final int BLOCKED = 0;
+        Object result;
+        Throwable t;
+
+        @Override
+        protected int tryAcquireShared(int ignore) {
+            return getState() == DONE ? 1 : -1;
+        }
+
+        @Override
+        protected boolean tryReleaseShared(int ignore) {
+            setState(DONE);
+            return true;
+        }
+
+        public void reset() {
+            setState(BLOCKED);
+        }
+
+        boolean isSignaled() {
+            return getState() == DONE;
+        }
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java
new file mode 100644
index 0000000..78a5fe2
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/Hex.java
@@ -0,0 +1,249 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.io;
+
+import java.io.ByteArrayOutputStream;
+
+/**
+ * Tables useful when converting byte arrays to and from strings of hexadecimal
+ * digits.
+ * Code from Ajp11, from Apache's JServ.
+ *
+ * @author Craig R. McClanahan
+ */
+
+public final class Hex {
+
+
+    // -------------------------------------------------------------- Constants
+
+    /**
+     *  Table for HEX to DEC byte translation.
+     */
+    public static final int[] DEC = {
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        00, 01, 02, 03, 04, 05, 06, 07,  8,  9, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    };
+
+
+    /**
+     * Table for DEC to HEX byte translation.
+     */
+    public static final byte[] HEX =
+    { (byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5',
+      (byte) '6', (byte) '7', (byte) '8', (byte) '9', (byte) 'a', (byte) 'b',
+      (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f' };
+
+
+    // --------------------------------------------------------- Static Methods
+
+
+    /**
+     * Convert a String of hexadecimal digits into the corresponding
+     * byte array by encoding each two hexadecimal digits as a byte.
+     *
+     * @param digits Hexadecimal digits representation
+     *
+     * @exception IllegalArgumentException if an invalid hexadecimal digit
+     *  is found, or the input string contains an odd number of hexadecimal
+     *  digits
+     */
+    public static byte[] convert(String digits) {
+
+	ByteArrayOutputStream baos = new ByteArrayOutputStream();
+	for (int i = 0; i < digits.length(); i += 2) {
+	    char c1 = digits.charAt(i);
+	    if ((i+1) >= digits.length())
+	        throw new IllegalArgumentException
+		        ("hexUtil.odd");
+	    char c2 = digits.charAt(i + 1);
+	    byte b = 0;
+	    if ((c1 >= '0') && (c1 <= '9'))
+		b += ((c1 - '0') * 16);
+	    else if ((c1 >= 'a') && (c1 <= 'f'))
+		b += ((c1 - 'a' + 10) * 16);
+	    else if ((c1 >= 'A') && (c1 <= 'F'))
+		b += ((c1 - 'A' + 10) * 16);
+	    else
+		throw new IllegalArgumentException
+		    ("hexUtil.bad");
+	    if ((c2 >= '0') && (c2 <= '9'))
+		b += (c2 - '0');
+	    else if ((c2 >= 'a') && (c2 <= 'f'))
+		b += (c2 - 'a' + 10);
+	    else if ((c2 >= 'A') && (c2 <= 'F'))
+		b += (c2 - 'A' + 10);
+	    else
+		throw new IllegalArgumentException
+		    ("hexUtil.bad");
+	    baos.write(b);
+	}
+	return (baos.toByteArray());
+
+    }
+
+
+    /**
+     * Convert a byte array into a printable format containing a
+     * String of hexadecimal digit characters (two per byte).
+     *
+     * @param bytes Byte array representation
+     */
+    public static String convert(byte bytes[]) {
+
+	StringBuffer sb = new StringBuffer(bytes.length * 2);
+	for (int i = 0; i < bytes.length; i++) {
+	    sb.append(convertDigit((bytes[i] >> 4)));
+	    sb.append(convertDigit((bytes[i] & 0x0f)));
+	}
+	return (sb.toString());
+
+    }
+
+
+    /**
+     * Convert 4 hex digits to an int, and return the number of converted
+     * bytes.
+     *
+     * @param hex Byte array containing exactly four hexadecimal digits
+     *
+     * @exception IllegalArgumentException if an invalid hexadecimal digit
+     *  is included
+     */
+    public static int convert2Int( byte[] hex ) {
+	// Code from Ajp11, from Apache's JServ
+
+	// assert b.length==4
+	// assert valid data
+	int len;
+	if(hex.length < 4 ) return 0;
+	if( DEC[hex[0]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len = DEC[hex[0]];
+	len = len << 4;
+	if( DEC[hex[1]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len += DEC[hex[1]];
+	len = len << 4;
+	if( DEC[hex[2]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len += DEC[hex[2]];
+	len = len << 4;
+	if( DEC[hex[3]]<0 )
+	    throw new IllegalArgumentException("hexUtil.bad");
+	len += DEC[hex[3]];
+	return len;
+    }
+
+
+
+    /**
+     * Provide a mechanism for ensuring this class is loaded.
+     */
+    public static void load() {
+        // Nothing to do
+    }
+
+    /**
+     * [Private] Convert the specified value (0 .. 15) to the corresponding
+     * hexadecimal digit.
+     *
+     * @param value Value to be converted
+     */
+    private static char convertDigit(int value) {
+
+	value &= 0x0f;
+	if (value >= 10)
+	    return ((char) (value - 10 + 'a'));
+	else
+	    return ((char) (value + '0'));
+
+    }
+
+    /**
+     * <code>getHexValue</code> displays a formatted hex
+     * representation of the passed byte array.  It also
+     * allows for only a specified offset and length of
+     * a particular array to be returned.
+     *
+     * @param bytes <code>byte[]</code> array to process.
+     * @param pos offset to begin processing.
+     * @param len number of bytes to process.
+     * @return <code>String</code> formatted hex representation of processed
+     *         array.
+     */
+    public static String getHexDump(byte[] bytes, int pos, int len,
+                                     boolean displayOffset) {
+        StringBuffer out = new StringBuffer( len * 2 );
+
+        for (int j = 0; j < len; j += 16) {
+            hexLine(out, bytes, pos + j, pos + len, displayOffset);
+        }
+
+        return out.toString();
+    }
+
+    private static void hexLine(StringBuffer out,
+                                byte[] bytes, int start, int end,
+                                boolean displayOffset) {
+
+        if ( displayOffset ) {
+            out.append(convertDigit((int) (start >> 12)));
+            out.append(convertDigit((int) (start >> 8)));
+            out.append(convertDigit((int) (start >> 4)));
+            out.append(convertDigit(start & 0x0F));
+            out.append(": ");
+        }
+        for (int i = start; i < start + 16; i++) {
+
+            if (i < end) {
+                out.append(convertDigit((int) (bytes[i] >> 4)));
+                out.append(convertDigit(bytes[i] & 0x0F));
+                out.append(" ");
+            } else {
+                out.append("   ");
+            }
+        }
+
+        out.append(" | ");
+
+        for (int i = start; i < start + 16 && i < end; i++) {
+            if( ! Character.isISOControl( (char)bytes[i] )) {
+                out.append( new Character((char)bytes[i]) );
+            } else {
+                out.append( "." );
+            }
+        }
+
+        out.append("\n");
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java
new file mode 100644
index 0000000..04ac268
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOBuffer.java
@@ -0,0 +1,698 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.logging.Logger;
+
+
+// TODO: append() will trigger callbacks - do it explicitely !!!
+// TODO: queue() shouldn't modify the buffer
+
+
+/**
+ * A list of data buckets.
+ *
+ * @author Costin Manolache
+ */
+public class IOBuffer {
+    static Logger log = Logger.getLogger("IOBrigade");
+
+    static int ALLOC_SIZE = 8192;
+    long defaultTimeout = Long.MAX_VALUE;
+
+    private LinkedList<BBucket> buffers = new LinkedList<BBucket>();
+
+    // close() has been called for out,
+    // or EOF/FIN received for in. It may still have data.
+    boolean closeQueued;
+
+    // Will be signalled (open) when there is data in the buffer.
+    // also used to sync on.
+    FutureCallbacks<IOBuffer> hasDataLock = new FutureCallbacks<IOBuffer>() {
+        protected boolean isSignaled() {
+            return hasData();
+        }
+    };
+
+    // may be null
+    protected IOChannel ch;
+
+    // Support for appending - needs improvements.
+    // appendable buffer is part of the buffer list if it has
+    // data, and kept here if empty.
+    BBuffer appendable;
+    boolean appending = false;
+    ByteBuffer writeBuffer;
+
+
+    public IOBuffer() {
+    }
+
+    public IOBuffer(IOChannel ch) {
+        this.ch = ch;
+    }
+
+    public IOChannel getChannel() {
+        return ch;
+    }
+
+    // ===== Buffer access =====
+
+
+    /**
+     * Return first non-empty buffer.
+     *
+     * The append buffer is part of the buffer list, and is left alone and
+     * empty.
+     *
+     * @return
+     */
+    public BBucket peekFirst() {
+        synchronized (buffers) {
+            BBucket o = (buffers.size() == 0) ? null : buffers.getFirst();
+
+            while (true) {
+                boolean empty = o == null || isEmpty(o);
+                if (o == null) {
+                    //hasDataLock.reset();
+                    return null; // no data in buffers
+                }
+                // o != null
+                if (empty) {
+                    buffers.removeFirst();
+                    o = (buffers.size() == 0) ? null : buffers.getFirst();
+                } else {
+                    return o;
+                }
+            }
+        }
+    }
+
+    public BBucket peekBucket(int idx) {
+        synchronized (buffers) {
+            return buffers.get(idx);
+        }
+    }
+
+
+    public void advance(int len) {
+        while (len > 0) {
+            BBucket first = peekFirst();
+            if (first == null) {
+                return;
+            }
+            if (len > first.remaining()) {
+                len -= first.remaining();
+                first.position(first.limit());
+            } else {
+                first.position(first.position() + len);
+                len = 0;
+            }
+        }
+    }
+
+    public void queue(String s) throws IOException {
+        // TODO: decode with prober charset
+        byte[] bytes = s.getBytes("UTF8");
+        queueInternal(BBuffer.wrapper(bytes, 0, bytes.length));
+    }
+
+    public void queue(BBuffer bc) throws IOException {
+        queueInternal(bc);
+    }
+
+    public void queue(Object bb) throws IOException {
+        queueInternal(bb);
+    }
+
+    private void queueInternal(Object bb) throws IOException {
+        if (closeQueued) {
+            throw new IOException("Closed");
+        }
+        synchronized (buffers) {
+            if (appending) {
+                throw new RuntimeException("Unexpected queue while " +
+                                "appending");
+            }
+            BBucket add = wrap(bb);
+            buffers.add(add);
+            //log.info("QUEUED: " + add.remaining() + " " + this);
+            notifyDataAvailable(add);
+        }
+
+    }
+
+    public int getBufferCount() {
+        peekFirst();
+        synchronized (buffers) {
+            return buffers.size();
+        }
+    }
+
+    public void clear() {
+        synchronized (buffers) {
+            buffers.clear();
+        }
+    }
+
+    public void recycle() {
+        closeQueued = false;
+        clear();
+        // Normally unlocked
+        hasDataLock.recycle();
+
+        appending = false;
+        appendable = null;
+    }
+
+    // ===================
+    /**
+     * Closed for append. It may still have data.
+     * @return
+     */
+    public boolean isClosedAndEmpty() {
+        return closeQueued && 0 == getBufferCount();
+    }
+
+
+    /**
+     * Mark as closed - but will not send data.
+     */
+    public void close() throws IOException {
+        if (closeQueued) {
+            return;
+        }
+        closeQueued = true;
+        notifyDataAvailable(null);
+    }
+
+
+    private boolean isEmpty(BBucket o) {
+        if (o instanceof BBucket &&
+                ((BBucket) o).remaining() == 0) {
+            return true;
+        }
+        return false;
+    }
+
+    private BBucket wrap(Object src) {
+        if (src instanceof byte[]) {
+            return BBuffer.wrapper((byte[]) src, 0, ((byte[]) src).length);
+        }
+        if (src instanceof ByteBuffer) {
+            //return src;
+            ByteBuffer bb = (ByteBuffer) src;
+            return BBuffer.wrapper(bb.array(), bb.position(),
+                        bb.remaining());
+        }
+        if (src instanceof byte[]) {
+            byte[] bb = (byte[]) src;
+            return BBuffer.wrapper(bb, 0, bb.length);
+        }
+        return (BBucket) src;
+    }
+
+    protected void notifyDataAvailable(Object bb) throws IOException {
+        synchronized (hasDataLock) {
+            hasDataLock.signal(this); // or bb ?
+        }
+    }
+
+    public boolean hasData() {
+        return closeQueued || peekFirst() != null;
+    }
+
+    public void waitData(long timeMs) throws IOException {
+        if (timeMs == 0) {
+            timeMs = defaultTimeout;
+        }
+        synchronized (hasDataLock) {
+            if (hasData()) {
+                return;
+            }
+            hasDataLock.reset();
+        }
+        hasDataLock.waitSignal(timeMs);
+    }
+
+
+    public boolean isAppendClosed() {
+        return closeQueued;
+    }
+
+    // =================== Helper methods ==================
+
+    /**
+     * Non-blocking read.
+     *
+     * @return -1 if EOF, -2 if no data available, or 0..255 for normal read.
+     */
+    public int read() throws IOException {
+        if (isClosedAndEmpty()) {
+            return -1;
+        }
+        BBucket bucket = peekFirst();
+        if (bucket == null) {
+            return -2;
+        }
+        int res = bucket.array()[bucket.position()];
+        bucket.position(bucket.position() + 1);
+        return res & 0xFF;
+    }
+
+    public int peek() throws IOException {
+        BBucket bucket = peekFirst();
+        if (bucket == null) {
+            return -1;
+        }
+        int res = bucket.array()[bucket.position()];
+        return res;
+    }
+
+    public int find(char c) {
+        int pos = 0;
+        for (int i = 0; i < buffers.size(); i++) {
+            BBucket bucket = buffers.get(i);
+            if (bucket == null || bucket.remaining() == 0) {
+                continue;
+            }
+            int found= BBuffer.findChar(bucket.array(), bucket.position(),
+                    bucket.limit(), c);
+            if (found >= 0) {
+                return pos + found;
+            }
+            pos += bucket.remaining();
+        }
+        return -1;
+    }
+
+    public int readLine(BBuffer bc) throws IOException {
+        return readToDelim(bc, '\n');
+    }
+
+    /**
+     * Copy up to and including "delim".
+     *
+     * @return number of bytes read, or -1 for end of stream.
+     */
+    int readToDelim(BBuffer bc, int delim) throws IOException {
+        int len = 0;
+        for (int idx = 0; idx < buffers.size(); idx++) {
+            BBucket bucket = buffers.get(idx);
+            if (bucket == null || bucket.remaining() == 0) {
+                continue;
+            }
+            byte[] data = bucket.array();
+            int end = bucket.limit();
+            int start = bucket.position();
+            for (int i = start; i < end; i++) {
+                byte chr = data[i];
+                bc.put(chr);
+                if (chr == delim) {
+                    bucket.position(i + 1);
+                    len += (i - start + 1);
+                    return len;
+                }
+            }
+            bucket.position(end); // empty - should be removed
+        }
+        if (len == 0 && isClosedAndEmpty()) {
+            return -1;
+        }
+        return len;
+    }
+
+
+    public int write(ByteBuffer bb) throws IOException {
+        int len = bb.remaining();
+        int pos = bb.position();
+        if (len == 0) {
+            return 0;
+        }
+        append(bb);
+        bb.position(pos + len);
+        return len;
+    }
+
+    public int read(byte[] buf, int off, int len) throws IOException {
+        if (isClosedAndEmpty()) {
+            return -1;
+        }
+        int rd = 0;
+        while (true) {
+            BBucket bucket = peekFirst();
+            if (bucket == null) {
+                return rd;
+            }
+            int toCopy = Math.min(len, bucket.remaining());
+            System.arraycopy(bucket.array(), bucket.position(),
+                    buf, off + rd, toCopy);
+            bucket.position(bucket.position() + toCopy);
+            rd += toCopy;
+            len -= toCopy;
+            if (len == 0) {
+                return rd;
+            }
+        }
+
+    }
+
+    public int read(BBuffer bb, int len) throws IOException {
+        bb.makeSpace(len);
+        int rd = read(bb.array(), bb.limit(), len);
+        if (rd < 0) {
+            return rd;
+        }
+        bb.limit(bb.limit() + rd);
+        return rd;
+    }
+
+    /**
+     * Non-blocking read.
+     */
+    public int read(ByteBuffer bb) {
+        if (isClosedAndEmpty()) {
+            return -1;
+        }
+        int len = 0;
+        while (true) {
+            int space = bb.remaining(); // to append
+            if (space == 0) {
+                return len;
+            }
+            BBucket first = peekFirst();
+            if (first == null) {
+                return len;
+            }
+            BBucket iob = ((BBucket) first);
+            if (space > iob.remaining()) {
+                space = iob.remaining();
+            }
+            bb.put(iob.array(), iob.position(), space);
+
+            iob.position(iob.position() + space);
+            iob.release();
+            len += space;
+        }
+    }
+
+
+    public BBuffer readAll(BBuffer chunk) throws IOException {
+        if (chunk == null) {
+            chunk = allocate();
+        }
+        while (true) {
+            if (isClosedAndEmpty()) {
+                return chunk;
+            }
+            BBucket first = peekFirst();
+            if (first == null) {
+                return chunk;
+            }
+            BBucket iob = ((BBucket) first);
+            chunk.append(iob.array(), iob.position(), iob.remaining());
+            iob.position(iob.position() + iob.remaining());
+            iob.release();
+
+        }
+    }
+
+    private BBuffer allocate() {
+        int size = 0;
+        for (int i = 0; i < getBufferCount(); i++) {
+            BBucket first = peekBucket(i);
+            if (first != null) {
+                size += first.remaining();
+            }
+        }
+        return BBuffer.allocate(size);
+    }
+
+    public BBuffer copyAll(BBuffer chunk) throws IOException {
+        if (chunk == null) {
+            chunk = allocate();
+        }
+        for (int i = 0; i < getBufferCount(); i++) {
+            BBucket iob = peekBucket(i);
+            chunk.append(iob.array(), iob.position(), iob.remaining());
+        }
+        return chunk;
+    }
+
+    public IOBuffer append(InputStream is) throws IOException {
+        while (true) {
+            ByteBuffer bb = getWriteBuffer();
+            int rd = is.read(bb.array(), bb.position(), bb.remaining());
+            if (rd <= 0) {
+                return this;
+            }
+            bb.position(bb.position() + rd);
+            releaseWriteBuffer(rd);
+        }
+    }
+
+    public IOBuffer append(BBuffer bc) throws IOException {
+        return append(bc.array(), bc.getStart(), bc.getLength());
+    }
+
+    public IOBuffer append(byte[] data) throws IOException {
+        return append(data, 0, data.length);
+    }
+
+    public IOBuffer append(byte[] data, int start, int len) throws IOException {
+        if (closeQueued) {
+            throw new IOException("Closed");
+        }
+        ByteBuffer bb = getWriteBuffer();
+
+        int i = start;
+        int end = start + len;
+        while (i < end) {
+            int rem = Math.min(end - i, bb.remaining());
+            // to write
+            bb.put(data, i, rem);
+            i += rem;
+            if (bb.remaining() < 8) {
+                releaseWriteBuffer(1);
+                bb = getWriteBuffer();
+            }
+        }
+
+        releaseWriteBuffer(1);
+        return this;
+    }
+
+    public IOBuffer append(int data) throws IOException {
+        if (closeQueued) {
+            throw new IOException("Closed");
+        }
+        ByteBuffer bb = getWriteBuffer();
+        bb.put((byte) data);
+        releaseWriteBuffer(1);
+        return this;
+    }
+
+    public IOBuffer append(ByteBuffer cs) throws IOException {
+        return append(cs.array(), cs.position() + cs.arrayOffset(),
+                cs.remaining());
+    }
+
+    /**
+     *  Append a buffer. The buffer will not be modified.
+     */
+    public IOBuffer append(BBucket cs) throws IOException {
+        append(cs.array(), cs.position(), cs.remaining());
+        return this;
+    }
+
+    /**
+     *  Append a buffer. The buffer will not be modified.
+     */
+    public IOBuffer append(BBucket cs, int len) throws IOException {
+        append(cs.array(), cs.position(), len);
+        return this;
+    }
+
+    public IOBuffer append(IOBuffer cs) throws IOException {
+        for (int i = 0; i < cs.getBufferCount(); i++) {
+            BBucket o = cs.peekBucket(i);
+            append(o);
+        }
+
+        return this;
+    }
+
+    public IOBuffer append(IOBuffer cs, int len) throws IOException {
+        for (int i = 0; i < cs.getBufferCount(); i++) {
+            BBucket o = cs.peekBucket(i);
+            append(o);
+        }
+
+        return this;
+    }
+
+    public IOBuffer append(CharSequence cs) throws IOException {
+        byte[] data = cs.toString().getBytes();
+        append(data, 0, data.length);
+        return this;
+    }
+
+    public IOBuffer append(char c) throws IOException {
+        ByteBuffer bb = getWriteBuffer();
+        bb.put((byte) c);
+        releaseWriteBuffer(1);
+        return this;
+    }
+
+    /**
+     * All operations that iterate over buffers must be
+     * sync
+     * @return
+     */
+    public synchronized int available() {
+        int a = 0;
+        int cnt = buffers.size();
+        for (int i = 0; i < cnt; i++) {
+            a += buffers.get(i).remaining();
+        }
+        return a;
+    }
+
+    public String toString() {
+        return "IOB:{c:" + getBufferCount() +
+          ", b:" + available() +
+          (isAppendClosed() ? ", C}" : " }");
+    }
+
+    public BBucket popLen(int lenToConsume) {
+        BBucket o = peekFirst(); // skip empty
+        if (o == null) {
+            return null;
+        }
+        BBucket sb = BBuffer.wrapper(o.array(),
+                o.position(), lenToConsume);
+        o.position(o.position() + lenToConsume);
+        return sb;
+    }
+
+    public BBucket popFirst() {
+        BBucket o = peekFirst(); // skip empty
+        if (o == null) {
+            return null;
+        }
+        if (o == appendable) {
+            synchronized (buffers) {
+                    // TODO: concurrency ???
+                    BBucket sb =
+                        BBuffer.wrapper(appendable.array(),
+                                appendable.position(),
+                                appendable.limit() - appendable.position());
+                    appendable.position(appendable.limit());
+                    return sb;
+            }
+        } else {
+            buffers.removeFirst();
+        }
+        return o;
+    }
+
+
+    public ByteBuffer getWriteBuffer() throws IOException {
+        synchronized (buffers) {
+            if (closeQueued) {
+                throw new IOException("Closed");
+            }
+            BBucket last = (buffers.size() == 0) ?
+                    null : buffers.getLast();
+            if (last == null || last != appendable ||
+                    last.array().length - last.limit() < 16) {
+                last = BBuffer.allocate(ALLOC_SIZE);
+            }
+            appending = true;
+            appendable = (BBuffer) last;
+
+            if (writeBuffer == null || writeBuffer.array() != appendable.array()) {
+                writeBuffer = ByteBuffer.wrap(appendable.array());
+            }
+            writeBuffer.position(appendable.limit());
+            writeBuffer.limit(appendable.array().length);
+            return writeBuffer;
+        }
+    }
+
+    public void releaseWriteBuffer(int read) throws IOException {
+        synchronized (buffers) {
+            if (!appending) {
+                throw new IOException("Not appending");
+            }
+            if (writeBuffer != null) {
+                if (appendable.limit() != writeBuffer.position()) {
+                    appendable.limit(writeBuffer.position());
+                    // We have some more data.
+                    if (buffers.size() == 0 ||
+                            buffers.getLast() != appendable) {
+                        buffers.add(appendable);
+                    }
+                    notifyDataAvailable(appendable);
+                }
+            }
+            appending = false;
+        }
+    }
+
+
+    // ------ More utilities - for parsing request ( later )-------
+//  public final int skipBlank(ByteBuffer bb, int start) {
+//  // Skipping blank lines
+//  byte chr = 0;
+//  do {
+//    if (!bb.hasRemaining()) {
+//      return -1;
+//    }
+//    chr = bb.get();
+//  } while ((chr == HttpParser.CR) || (chr == HttpParser.LF));
+//  return bb.position();
+//}
+
+//public final int readToDelimAndLowerCase(ByteBuffer bb,
+//                                         byte delim,
+//                                         boolean lower) {
+//  boolean space = false;
+//  byte chr = 0;
+//  while (!space) {
+//    if (!bb.hasRemaining()) {
+//      return -1;
+//    }
+//    chr = bb.get();
+//    if (chr == delim) {
+//      space = true;
+//    }
+//    if (lower && (chr >= HttpParser.A) && (chr <= HttpParser.Z)) {
+//      bb.put(bb.position() - 1,
+//          (byte) (chr - HttpParser.LC_OFFSET));
+//    }
+//  }
+//  return bb.position();
+//}
+
+//public boolean skipSpace(ByteBuffer bb) {
+//  boolean space = true;
+//  while (space) {
+//    if (!bb.hasRemaining()) {
+//      return false;
+//    }
+//    byte chr = bb.get();
+//    if ((chr == HttpParser.SP) || (chr == HttpParser.HT)) {
+//      //
+//    } else {
+//      space = false;
+//      bb.position(bb.position() -1); // move back
+//    }
+//  }
+//  return true;
+//}
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java
new file mode 100644
index 0000000..a7db4df
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOChannel.java
@@ -0,0 +1,371 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+
+
+
+/**
+ * Buffered, non-blocking ByteChannel.
+ *
+ * write() data will be added to the buffer. Call startSending() to
+ * flush.
+ *
+ *
+ *
+ * - you can use it as a normal non-blocking ByteChannel.
+ * - you can call getRead
+ *
+ * Very different from MINA IoFilters, also much lower level.
+ *
+ *
+ * @author Costin Manolache
+ */
+public abstract class IOChannel implements ByteChannel, IOConnector.DataReceivedCallback,
+        IOConnector.DataFlushedCallback {
+
+    /**
+     * If this channel wraps another channel - for example a socket.
+     * Will be null if this is the 'root' channel - a socket, memory.
+     */
+    protected IOChannel net;
+
+    /**
+     * Set with another channel layered on top of the current channel.
+     */
+    protected IOChannel head;
+
+    protected String id;
+
+    /**
+     * A string that can be parsed to extract the target.
+     * host:port for normal sockets
+     */
+    protected CharSequence target;
+
+    /**
+     * Connector that created the channel.
+     */
+    protected IOConnector connector;
+
+    /**
+     * Callbacks. Will be moved if a new head is inserted.
+     */
+    protected IOConnector.ConnectedCallback connectedCallback;
+
+    /**
+     * Will be called if any data is received.
+     * Will also be called on close. Close with lastException set indicates
+     * an error condition.
+     */
+    protected IOConnector.DataReceivedCallback dataReceivedCallback;
+
+    /**
+     * Out data is buffered, then sent with startSending.
+     * This callback indicates the data has been sent. Can be used
+     * to implement blocking flush.
+     */
+    protected IOConnector.DataFlushedCallback dataFlushedCallback;
+
+    // Last activity timestamp.
+    // TODO: update and use it ( placeholder )
+    public long ts;
+
+    /**
+     * If an async exception happens.
+     */
+    protected Throwable lastException;
+
+    protected IOChannel() {
+    }
+
+    public void setConnectedCallback(IOConnector.ConnectedCallback connectedCallback) {
+        this.connectedCallback = connectedCallback;
+    }
+
+    public void setDataReceivedCallback(IOConnector.DataReceivedCallback dataReceivedCallback) {
+        this.dataReceivedCallback = dataReceivedCallback;
+    }
+
+    /**
+     * Callback called when the bottom ( OS ) channel has finished flushing.
+     *
+     * @param dataFlushedCallback
+     */
+    public void setDataFlushedCallback(IOConnector.DataFlushedCallback dataFlushedCallback) {
+        this.dataFlushedCallback = dataFlushedCallback;
+    }
+
+    // Input
+    public abstract IOBuffer getIn();
+
+    // Output
+    public abstract IOBuffer getOut();
+
+
+    /**
+     * From downstream ( NET ). Pass it to the next channel.
+     */
+    public void handleReceived(IOChannel net) throws IOException {
+        sendHandleReceivedCallback();
+    }
+
+    /**
+     * Called from lower layer (NET) when the last flush is
+     * done and all buffers have been sent to OS ( or
+     * intended recipient ).
+     *
+     * Will call the callback or next filter, may do additional
+     * processing.
+     *
+     * @throws IOException
+     */
+    public void handleFlushed(IOChannel net) throws IOException {
+        sendHandleFlushedCallback();
+    }
+
+    private void sendHandleFlushedCallback() throws IOException {
+        try {
+            if (dataFlushedCallback != null) {
+                dataFlushedCallback.handleFlushed(this);
+            }
+            if (head != null) {
+                head.handleFlushed(this);
+            }
+        } catch (Throwable t) {
+            close();
+            if (t instanceof IOException) {
+                throw (IOException) t;
+            } else {
+                throw new WrappedException("Error in handleFlushed", t);
+            }
+        }
+    }
+
+
+    /**
+     * Notify next channel or callback that data has been received.
+     * Called after a lower channel gets more data ( in the IOThread
+     * for example ).
+     *
+     * Also called when closed stream is detected. Can be called
+     * to just force upper layers to check for data.
+     */
+    public void sendHandleReceivedCallback() throws IOException {
+        try {
+            if (dataReceivedCallback != null) {
+                dataReceivedCallback.handleReceived(this);
+            }
+            if (head != null) {
+                head.handleReceived(this);
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+            try {
+                close();
+            } catch(Throwable t2) {
+                t2.printStackTrace();
+            }
+            if (t instanceof IOException) {
+                throw (IOException) t;
+            } else {
+                throw new WrappedException(t);
+            }
+        }
+    }
+
+    /**
+     * Return last IO exception.
+     *
+     * The channel is async, exceptions can happen at any time.
+     * The normal callback will be called ( connected, received ), it
+     * should check if the channel is closed and the exception.
+     */
+    public Throwable lastException() {
+        return lastException;
+    }
+
+    public void close() throws IOException {
+        shutdownOutput();
+        // Should it read the buffers ?
+
+        if (getIn() == null || getIn().isAppendClosed()) {
+            return;
+        } else {
+            getIn().close();
+            sendHandleReceivedCallback();
+        }
+        getIn().hasDataLock.signal(getIn());
+    }
+
+    public boolean isOpen() {
+        return getIn() != null &&
+        getOut() != null &&
+        !getIn().isAppendClosed() && !getOut().isAppendClosed();
+    }
+
+    public void shutdownOutput() throws IOException {
+        if (getOut() == null || getOut().isAppendClosed()) {
+            return;
+        } else {
+            getOut().close();
+            startSending();
+        }
+    }
+
+    public void setSink(IOChannel previous) throws IOException {
+        this.net = previous;
+    }
+
+    public IOChannel getSink() {
+        return net;
+    }
+
+    // Chaining/filtering
+
+    /**
+     * Called to add an filter after the current channel, for
+     * example set SSL on top of a socket channel.
+     *
+     * The 'next' channel will have the received/flushed callbacks
+     * of the current channel. The current channel's callbacks will
+     * be reset.
+     *
+     * "Head" is from STREAMS.
+     *
+     * @throws IOException
+     */
+    public IOChannel setHead(IOChannel head) throws IOException {
+        this.head = head;
+        head.setSink(this);
+
+        // TODO: do we want to migrate them automatically ?
+        head.setDataReceivedCallback(dataReceivedCallback);
+        head.setDataFlushedCallback(dataFlushedCallback);
+        // app.setClosedCallback(closedCallback);
+
+        dataReceivedCallback = null;
+        dataFlushedCallback = null;
+        return this;
+    }
+
+    public IOChannel getFirst() {
+        IOChannel first = this;
+        while (true) {
+            if (!(first instanceof IOChannel)) {
+                return first;
+            }
+            IOChannel before = ((IOChannel) first).getSink();
+            if (before == null) {
+                return first;
+            } else {
+                first = before;
+            }
+        }
+    }
+
+    // Socket support
+
+    public void readInterest(boolean b) throws IOException {
+        if (net != null) {
+            net.readInterest(b);
+        }
+    }
+
+    // Helpers
+
+    public int read(ByteBuffer bb) throws IOException {
+        return getIn().read(bb);
+    }
+
+    public int readNonBlocking(ByteBuffer bb) throws IOException {
+        return getIn().read(bb);
+    }
+
+    public void waitFlush(long timeMs) throws IOException {
+        return;
+    }
+
+    public int readBlocking(ByteBuffer bb, long timeMs) throws IOException {
+        getIn().waitData(timeMs);
+        return getIn().read(bb);
+    }
+
+    /**
+     * Capture all output in a buffer.
+     */
+    public BBuffer readAll(BBuffer chunk, long to)
+            throws IOException {
+        if (chunk == null) {
+            chunk = BBuffer.allocate();
+        }
+        while (true) {
+            getIn().waitData(to);
+            BBucket next = getIn().peekFirst();
+            if (getIn().isClosedAndEmpty() && next == null) {
+                return chunk;
+            }
+            if (next == null) {
+                continue; // false positive
+            }
+            chunk.append(next.array(), next.position(), next.remaining());
+            getIn().advance(next.remaining());
+        }
+    }
+
+    public int write(ByteBuffer bb) throws IOException {
+        return getOut().write(bb);
+    }
+
+    public void write(byte[] data) throws IOException {
+        getOut().append(data, 0, data.length);
+    }
+
+    public void write(String string) throws IOException {
+        write(string.getBytes());
+    }
+
+    /**
+     * Send data in out to the intended recipient.
+     * This is not blocking.
+     */
+    public abstract void startSending() throws IOException;
+
+
+    public void setId(String id) {
+        this.id = id;
+    }
+
+    public String getId() {
+        return id;
+    }
+
+    public CharSequence getTarget() {
+        if (net != null) {
+            return net.getTarget();
+        }
+        return target;
+    }
+
+    public void setTarget(CharSequence target) {
+        this.target = target;
+    }
+
+    public static final String ATT_REMOTE_HOSTNAME = "RemoteHostname";
+    public static final String ATT_LOCAL_HOSTNAME = "LocalHostname";
+    public static final String ATT_REMOTE_PORT = "RemotePort";
+    public static final String ATT_LOCAL_PORT = "LocalPort";
+    public static final String ATT_LOCAL_ADDRESS = "LocalAddress";
+    public static final String ATT_REMOTE_ADDRESS = "RemoteAddress";
+
+    public Object getAttribute(String name) {
+        if (net != null) {
+            return net.getAttribute(name);
+        }
+        return null;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java
new file mode 100644
index 0000000..4344146
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOConnector.java
@@ -0,0 +1,66 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.util.Timer;
+
+
+/**
+ * Factory for IOChannels, with support for caching.
+ *
+ *
+ * @author Costin Manolache
+ */
+public abstract class IOConnector {
+
+    public static interface DataReceivedCallback {
+        /**
+         * Called when data or EOF has been received.
+         */
+        public void handleReceived(IOChannel ch) throws IOException;
+    }
+
+    /**
+     * Callback for accept and connect.
+     *
+     * Will also be called if an error happens while connecting, in
+     * which case the connection will be closed.
+     */
+    public static interface ConnectedCallback {
+        public void handleConnected(IOChannel ch) throws IOException;
+    }
+
+    public static interface DataFlushedCallback {
+        public void handleFlushed(IOChannel ch) throws IOException;
+    }
+
+    protected Timer timer;
+
+    public Timer getTimer() {
+        return timer;
+    }
+
+    /**
+     * If the connector is layered on top of a different connector,
+     * return the lower layer ( for example the socket connector)
+     */
+    public IOConnector getNet() {
+        return null;
+    }
+
+    public abstract void acceptor(IOConnector.ConnectedCallback sc,
+                         CharSequence port, Object extra)
+        throws IOException;
+
+    // TODO: failures ?
+    // TODO: use String target or url
+    public abstract void connect(String host, int port,
+            IOConnector.ConnectedCallback sc) throws IOException;
+
+    public void stop() {
+        if (timer != null) {
+            timer.cancel();
+        }
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java
new file mode 100644
index 0000000..aebf5c5
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOInputStream.java
@@ -0,0 +1,71 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+
+/**
+ * Similar with ServletInputStream - adds readLine(byte[]..), using
+ * a IOBuffer.
+ *
+ *
+ *
+ * @author Costin Manolache
+ */
+public class IOInputStream extends InputStream {
+
+    IOBuffer bb;
+    long timeout;
+
+    public IOInputStream(IOChannel httpCh, long to) {
+        bb = httpCh.getIn();
+        this.timeout = to;
+    }
+
+    @Override
+    public int read() throws IOException {
+        // getReadableBucket/peekFirst returns a buffer with at least
+        // 1 byte in it.
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        bb.waitData(timeout);
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+
+        return bb.read();
+    }
+
+    public int read(byte[] buf, int off, int len) throws IOException {
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        bb.waitData(timeout);
+        if (bb.isClosedAndEmpty()) {
+            return -1;
+        }
+        return bb.read(buf, off, len);
+    }
+
+    /**
+     *  Servlet-style read line: terminator is \n or \r\n, left in buffer.
+     */
+    public int readLine(byte[] b, int off, int len) throws IOException {
+        if (len <= 0) {
+            return 0;
+        }
+        int count = 0, c;
+
+        while ((c = read()) != -1) {
+            b[off++] = (byte)c;
+            count++;
+            if (c == '\n' || count == len) {
+                break;
+            }
+        }
+        return count > 0 ? count : -1;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java
new file mode 100644
index 0000000..90a6e02
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOOutputStream.java
@@ -0,0 +1,204 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.CharConversionException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+import java.text.MessageFormat;
+
+/**
+ * Same methods with ServletOutputStream.
+ *
+ * There is no restriction in using the Writer and InputStream at the
+ * same time - the servlet layer will impose it for compat. You can also use
+ * IOBuffer directly.
+ *
+ * If you mix stream and writer:
+ *  - call BufferWriter.push() to make sure all chars are sent down
+ *  - the BufferOutputStream doesn't cache any data, all goes to the
+ *   IOBuffer.
+ *  - flush() on BufferOutputStream and BufferWriter will send the data
+ *  to the network and block until it gets to the socket ( so it can
+ *  throw exception ).
+ *  - You can also use non-blocking flush methods in IOBuffer, and a
+ *  callback  if you want to know when the write was completed.
+ *
+ * @author Costin Manolache
+ */
+public class IOOutputStream extends OutputStream {
+
+    IOBuffer bb;
+    IOChannel ch;
+    int bufferSize = 8 * 1024;
+
+    int wSinceFlush = 0;
+
+    public IOOutputStream(IOBuffer out, IOChannel httpMessage) {
+        this.bb = out;
+        ch = httpMessage;
+    }
+
+    public void recycle() {
+        wSinceFlush = 0;
+        bufferSize = 8 * 1024;
+    }
+
+    public void reset() {
+        wSinceFlush = 0;
+        bb.clear();
+    }
+
+    public int getWrittenSinceFlush() {
+        return wSinceFlush;
+    }
+
+
+    public int getBufferSize() {
+        return bufferSize;
+    }
+
+    public void setBufferSize(int size) {
+        if (size > bufferSize) {
+            bufferSize = size;
+        }
+    }
+
+    private void updateSize(int cnt) throws IOException {
+        wSinceFlush += cnt;
+        if (wSinceFlush > bufferSize) {
+            flush();
+        }
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        bb.append((char) b);
+        updateSize(1);
+    }
+
+    @Override
+    public void write(byte data[]) throws IOException {
+      write(data, 0, data.length);
+    }
+
+    @Override
+    public void write(byte data[], int start, int len) throws IOException {
+        bb.append(data, start, len);
+        updateSize(len);
+    }
+
+    public void flush() throws IOException {
+        if (ch != null) {
+            ch.startSending();
+
+            ch.waitFlush(Long.MAX_VALUE);
+        }
+        wSinceFlush = 0;
+    }
+
+    public void close() throws IOException {
+        flush();
+        bb.close();
+    }
+
+
+    public void write(ByteBuffer source) throws IOException {
+        write(source.array(), source.position(), source.remaining());
+        source.position(source.limit());
+    }
+
+    public void print(String s) throws IOException {
+        if (s==null) s="null";
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt (i);
+
+            //
+            // XXX NOTE:  This is clearly incorrect for many strings,
+            // but is the only consistent approach within the current
+            // servlet framework.  It must suffice until servlet output
+            // streams properly encode their output.
+            //
+            if ((c & 0xff00) != 0) {    // high order byte must be zero
+                String errMsg = "Not ISO-8859-1";
+                Object[] errArgs = new Object[1];
+                errArgs[0] = new Character(c);
+                errMsg = MessageFormat.format(errMsg, errArgs);
+                throw new CharConversionException(errMsg);
+            }
+            write (c);
+        }
+    }
+
+
+    public void print(boolean b) throws IOException {
+        String msg;
+        if (b) {
+            msg = "true";
+        } else {
+            msg = "false";
+        }
+        print(msg);
+    }
+
+    public void print(char c) throws IOException {
+        print(String.valueOf(c));
+    }
+
+    public void print(int i) throws IOException {
+        print(String.valueOf(i));
+    }
+
+    public void print(long l) throws IOException {
+        print(String.valueOf(l));
+    }
+
+    public void print(float f) throws IOException {
+        print(String.valueOf(f));
+    }
+
+    public void print(double d) throws IOException {
+        print(String.valueOf(d));
+    }
+
+    public void println() throws IOException {
+        print("\r\n");
+    }
+
+    public void println(String s) throws IOException {
+        print(s);
+        println();
+    }
+
+    public void println(boolean b) throws IOException {
+        print(b);
+        println();
+    }
+
+    public void println(char c) throws IOException {
+        print(c);
+        println();
+    }
+
+    public void println(int i) throws IOException {
+        print(i);
+        println();
+    }
+
+    public void println(long l) throws IOException {
+        print(l);
+        println();
+    }
+
+    public void println(float f) throws IOException {
+        print(f);
+        println();
+    }
+
+    public void println(double d) throws IOException {
+        print(d);
+        println();
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java
new file mode 100644
index 0000000..bbeacdc
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOReader.java
@@ -0,0 +1,236 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetDecoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.nio.charset.MalformedInputException;
+import java.nio.charset.UnmappableCharacterException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+
+/**
+ * Conversion from Bytes to Chars and support for decoding.
+ *
+ * Replaces tomcat B2CConverter with NIO equivalent. B2CConverter was a hack
+ * (re)using an dummy InputStream backed by a ByteChunk.
+ *
+ * @author Costin Manolache
+ */
+public class IOReader extends Reader {
+
+    IOBuffer iob;
+    Map<String, CharsetDecoder> decoders = new HashMap<String, CharsetDecoder>();
+    CharsetDecoder decoder;
+
+    private static boolean REUSE = true;
+    String enc;
+    private boolean closed;
+    public static final String DEFAULT_ENCODING = "ISO-8859-1";
+    long timeout = 0;
+
+    public IOReader(IOBuffer iob) {
+        this.iob = iob;
+    }
+
+    public void setTimeout(long to) {
+        timeout = to;
+    }
+
+    public void setEncoding(String charset) {
+        enc = charset;
+        if (enc == null) {
+            enc = DEFAULT_ENCODING;
+        }
+        decoder = REUSE ? decoders.get(enc) : null;
+        if (decoder == null) {
+            decoder = Charset.forName(enc).newDecoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            if (REUSE) {
+                decoders.put(enc, decoder);
+            }
+        }
+    }
+
+    public String getEncoding() {
+        return enc;
+    }
+
+    public void recycle() {
+        if (decoder != null) {
+            decoder.reset();
+        }
+        closed = false;
+        enc = null;
+    }
+
+    private void checkClosed() throws IOException {
+        if (closed) throw new IOException("closed");
+    }
+
+    public boolean ready() {
+        return iob.peekFirst() != null;
+    }
+
+    public int read(java.nio.CharBuffer target) throws IOException {
+        int len = target.remaining();
+        char[] cbuf = new char[len];
+        int n = read(cbuf, 0, len);
+        if (n > 0)
+            target.put(cbuf, 0, n);
+        return n;
+    }
+
+    public int read() throws IOException {
+        char cb[] = new char[1];
+        if (read(cb, 0, 1) == -1)
+            return -1;
+        else
+            return cb[0];
+    }
+
+    @Override
+    public void close() throws IOException {
+        closed = true;
+        iob.close();
+    }
+
+    /**
+     * Used if a bucket ends on a char boundary
+     */
+    BBuffer underFlowBuffer = BBuffer.allocate(10);
+    public static AtomicInteger underFlows = new AtomicInteger();
+
+    /**
+     * Decode all bytes - for example a URL or header.
+     */
+    public void decodeAll(BBucket bb, CBuffer c) {
+
+        while (bb.hasRemaining()) {
+            CharBuffer charBuffer = c.getAppendCharBuffer();
+            CoderResult res = decode1(bb, charBuffer, true);
+            c.returnAppendCharBuffer(charBuffer);
+            if (res != CoderResult.OVERFLOW) {
+                if (res == CoderResult.UNDERFLOW || bb.hasRemaining()) {
+                    System.err.println("Ignored trailing bytes " + bb.remaining());
+                }
+                return;
+            }
+        }
+
+    }
+
+    /**
+     * Do one decode pass.
+     */
+    public CoderResult decode1(BBucket bb, CharBuffer c, boolean eof) {
+        ByteBuffer b = bb.getByteBuffer();
+
+        if (underFlowBuffer.hasRemaining()) {
+            // Need to get rid of the underFlow first
+            for (int i = 0; i < 10; i++) {
+                underFlowBuffer.put(b.get());
+                bb.position(b.position());
+                ByteBuffer ub = underFlowBuffer.getByteBuffer();
+                CoderResult res = decoder.decode(ub, c, eof);
+                if (! ub.hasRemaining()) {
+                    // underflow resolved
+                    break;
+                }
+                if (res == CoderResult.OVERFLOW) {
+                    return res;
+                }
+            }
+            if (underFlowBuffer.hasRemaining()) {
+                throw new RuntimeException("Can't resolve underflow after " +
+                		"10 bytes");
+            }
+        }
+
+        CoderResult res = decoder.decode(b, c, eof);
+        bb.position(b.position());
+
+        if (res == CoderResult.UNDERFLOW && bb.hasRemaining()) {
+            // b ends on a boundary
+            underFlowBuffer.append(bb.array(), bb.position(), bb.remaining());
+            bb.position(bb.limit());
+        }
+        return res;
+    }
+
+    @Override
+    public int read(char[] cbuf, int offset, int length) throws IOException {
+        checkClosed();
+        if (length == 0) {
+            return 0;
+        }
+        // we can either allocate a new CharBuffer or use a
+        // static one and copy. Seems simpler this way - needs some
+        // load test, but InputStreamReader seems to do the same.
+        CharBuffer out = CharBuffer.wrap(cbuf, offset, length);
+
+        CoderResult result = CoderResult.UNDERFLOW;
+
+        BBucket bucket = iob.peekFirst();
+
+        // Consume as much as possible without blocking
+        while (result == CoderResult.UNDERFLOW) {
+            // fill the buffer if needed
+            if (bucket == null || ! bucket.hasRemaining()) {
+                if (out.position() > offset) {
+                    // we could return the result without blocking read
+                    break;
+                }
+                bucket = null;
+                while (bucket == null) {
+                    iob.waitData(timeout);
+                    bucket = iob.peekFirst();
+                    if (bucket == null && iob.isClosedAndEmpty()) {
+                        // EOF, we couldn't decode anything
+                        break;
+                    }
+                }
+
+                if (bucket == null) {
+                    // eof
+                    break;
+                }
+            }
+
+            result = decode1(bucket, out, false);
+        }
+
+        if (result == CoderResult.UNDERFLOW && iob.isClosedAndEmpty()) {
+            // Flush out any remaining data
+            ByteBuffer bytes = bucket == null ?
+                    underFlowBuffer.getByteBuffer() : bucket.getByteBuffer();
+            result = decoder.decode(bytes, out, true);
+            if (bucket == null) {
+                underFlowBuffer.position(bytes.position());
+            } else {
+                bucket.position(bytes.position());
+            }
+
+            decoder.flush(out);
+            decoder.reset();
+        }
+
+        if (result.isMalformed()) {
+            throw new MalformedInputException(result.length());
+        } else if (result.isUnmappable()) {
+            throw new UnmappableCharacterException(result.length());
+        }
+
+        int rd = out.position() - offset;
+        return rd == 0 ? -1 : rd;
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java
new file mode 100644
index 0000000..7d06cf1
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/IOWriter.java
@@ -0,0 +1,212 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.nio.charset.Charset;
+import java.nio.charset.CharsetEncoder;
+import java.nio.charset.CoderResult;
+import java.nio.charset.CodingErrorAction;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Converts chars to bytes, and associated encoding.
+ *
+ * Replaces C2B from old tomcat.
+ *
+ * @author Costin Manolache
+ */
+public class IOWriter extends Writer {
+
+    IOBuffer iob;
+    Map<String, CharsetEncoder> encoders = new HashMap<String, CharsetEncoder>();
+    CharsetEncoder encoder;
+
+    private static boolean REUSE = true;
+    String enc;
+    private boolean closed;
+    IOChannel ioCh;
+
+    public IOWriter(IOChannel iob) {
+        this.ioCh = iob;
+        if (iob != null) {
+            this.iob = iob.getOut();
+        }
+    }
+
+    public void setEncoding(String charset) {
+        if (charset == null) {
+            charset = "UTF-8";
+        }
+        enc = charset;
+        encoder = getEncoder(charset);
+        if (encoder == null) {
+            encoder = Charset.forName(charset).newEncoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            if (REUSE) {
+                encoders.put(charset, encoder);
+            }
+        }
+    }
+
+    CharsetEncoder getEncoder(String charset) {
+        if (charset == null) {
+            charset = "UTF-8";
+        }
+        encoder = REUSE ? encoders.get(charset) : null;
+        if (encoder == null) {
+            encoder = Charset.forName(charset).newEncoder()
+                .onMalformedInput(CodingErrorAction.REPLACE)
+                .onUnmappableCharacter(CodingErrorAction.REPLACE);
+            if (REUSE) {
+                encoders.put(charset, encoder);
+            }
+        }
+        return encoder;
+    }
+
+    public String getEncoding() {
+        return enc;
+    }
+
+    public void recycle() {
+        if (encoder != null) {
+            encoder.reset();
+        }
+        closed = false;
+        enc = null;
+    }
+
+
+    private void checkClosed() throws IOException {
+        if (closed) throw new IOException("closed");
+    }
+
+    @Override
+    public void close() throws IOException {
+        closed = true;
+        // flush the buffer ?
+        ByteBuffer out = iob.getWriteBuffer();
+        encoder.flush(out);
+        iob.releaseWriteBuffer(1);
+
+        iob.close();
+    }
+
+    /**
+     * Used if a bucket ends on a char boundary
+     */
+    CBuffer underFlowBuffer = CBuffer.newInstance();
+
+    public void encode1(CBuffer cc,
+            BBuffer bb, CharsetEncoder encoder, boolean eof) {
+        CharBuffer c = cc.getNioBuffer();
+        ByteBuffer b = bb.getWriteByteBuffer(c.remaining() * 2);
+        encode1(c, b, encoder, eof);
+        cc.returnNioBuffer(c);
+        bb.limit(b.position());
+    }
+
+    /**
+     *
+     * @param cc
+     * @return
+     */
+    public void encode1(CharBuffer c,
+            ByteBuffer b, CharsetEncoder encoder, boolean eof) {
+
+        // TODO: buffer growth in caller
+
+        CoderResult res = encoder.encode(c, b, eof);
+        if (res == CoderResult.OVERFLOW) {
+            // bb is full - next call will get a larger buffer ( it
+            // grows ) or maybe will be flushed.
+        }
+        if (res == CoderResult.UNDERFLOW && c.remaining() > 0 && !eof) {
+            // TODO: if eof -> exception ?
+            // cc has remaining chars - for example a surrogate start.
+            underFlowBuffer.put(c);
+        }
+
+    }
+
+    public void encodeAll(CBuffer cc,
+            BBuffer bb, CharsetEncoder encoder, boolean eof) {
+        while (cc.length() > 0) {
+            encode1(cc, bb, encoder, eof);
+        }
+    }
+
+    public void encodeAll(CBuffer cc,
+            BBuffer bb, String cs) {
+        encodeAll(cc, bb, getEncoder(cs), true);
+    }
+
+    @Override
+    public void flush() throws IOException {
+        if (ioCh != null) {
+            ioCh.startSending();
+        }
+    }
+
+    // TODO: use it for utf-8
+    public static int char2utf8(byte[] ba, int off, char c, char c1) {
+        int i = 0;
+        if (c < 0x80) {
+            ba[off++] = (byte) (c & 0xFF);
+            return 1;
+        } else if (c < 0x800)
+        {
+            ba[off++] = (byte) (0xC0 | c >> 6);
+            ba[off++] = (byte) (0x80 | c & 0x3F);
+            return 2;
+        }
+        else if (c < 0x10000)
+        {
+            ba[off++] = (byte) ((0xE0 | c >> 12));
+            ba[off++] = (byte) ((0x80 | c >> 6 & 0x3F));
+            ba[off++] = (byte) ((0x80 | c & 0x3F));
+            return 3;
+        }
+        else if (c < 0x200000)
+        {
+            ba[off++] = (byte) ((0xF0 | c >> 18));
+            ba[off++] = (byte) ((0x80 | c >> 12 & 0x3F));
+            ba[off++] = (byte) ((0x80 | c >> 6 & 0x3F));
+            ba[off++] = (byte) ((0x80 | c & 0x3F));
+            return 4;
+        }
+
+
+        return i;
+    }
+
+
+    /**
+     * Just send the chars to the byte[], without flushing down.
+     *
+     * @throws IOException
+     */
+    public void push() throws IOException {
+        // we don't cache here.
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        checkClosed();
+        CharBuffer cb = CharBuffer.wrap(cbuf, off, len);
+
+        while (cb.remaining() > 0) {
+            ByteBuffer wb = iob.getWriteBuffer();
+            encode1(cb, wb, encoder, false);
+            iob.releaseWriteBuffer(1);
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java
new file mode 100644
index 0000000..75fcc3b
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/MemoryIOConnector.java
@@ -0,0 +1,88 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.util.Timer;
+
+public class MemoryIOConnector extends IOConnector {
+
+    public static class MemoryIOChannel extends IOChannel {
+        IOBuffer netIn = new IOBuffer(this) {
+            protected void notifyDataAvailable(Object bb) throws IOException {
+                sendHandleReceivedCallback();
+                super.notifyDataAvailable(bb);
+            }
+        };
+        IOBuffer netOut = new IOBuffer(this);
+
+        /**
+         * All flushed output will be saved to 'out'.
+         */
+        public BBuffer out = BBuffer.allocate(4096);
+
+        public MemoryIOChannel() {
+        }
+
+        public void startSending() throws IOException {
+            //
+            IOBuffer bb = netOut;
+            while (true) {
+                if (bb.isClosedAndEmpty()) {
+                    break;
+                }
+                BBucket first = bb.peekFirst();
+                if (first == null) {
+                    break;
+                }
+                BBucket iob = ((BBucket) first);
+                out.append(iob.array(), iob.position(), iob.remaining());
+                bb.advance(iob.remaining());
+                iob.release();
+            }
+
+            handleFlushed(this);
+        }
+
+        @Override
+        public IOBuffer getIn() {
+            return netIn;
+        }
+        @Override
+        public IOBuffer getOut() {
+            return netOut;
+        }
+    }
+
+    // TODO: in-process communication without sockets for testing
+    ConnectedCallback acceptor;
+    MemoryIOConnector server;
+
+    public MemoryIOConnector() {
+        timer = new Timer(true);
+    }
+
+    public MemoryIOConnector withServer(MemoryIOConnector server) {
+        this.server = server;
+        return server;
+    }
+
+    @Override
+    public void acceptor(ConnectedCallback sc, CharSequence port, Object extra)
+            throws IOException {
+        this.acceptor = sc;
+    }
+
+    @Override
+    public void connect(String host, int port, ConnectedCallback sc)
+            throws IOException {
+        IOChannel ch = new MemoryIOChannel();
+        IOChannel sch = new MemoryIOChannel();
+        // TODO: mix
+        if (server != null && server.acceptor != null) {
+            server.acceptor.handleConnected(sch);
+        }
+        sc.handleConnected(ch);
+    }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java
new file mode 100644
index 0000000..7cc1408
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioChannel.java
@@ -0,0 +1,198 @@
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.Channel;
+
+
+/**
+ * Wrapper around the real channel, with selector-specific info.
+ *
+ * It is stored as an attachment in the selector.
+ */
+public class NioChannel implements ByteChannel {
+
+    public static interface NioChannelCallback {
+        public void handleConnected(NioChannel ch) throws IOException;
+        public void handleClosed(NioChannel ch) throws IOException;
+        public void handleReadable(NioChannel ch) throws IOException;
+        public void handleWriteable(NioChannel ch) throws IOException;
+
+    }
+
+    NioChannel(NioThread sel) {
+        this.sel = sel;
+    }
+
+    // APR long is wrapped in a ByteChannel as well - with few other longs.
+    Channel channel;
+
+    // sync access.
+    Object selKey;
+
+    NioThread sel;
+
+    /**
+     * If != 0 - the callback will be notified closely after this time.
+     * Used for timeouts.
+     */
+    long nextTimeEvent = 0;
+
+    // Callbacks
+    Runnable timeEvent;
+
+    NioChannelCallback callback;
+
+
+    Throwable lastException;
+
+    // True if the callback wants to be notified of read/write
+    boolean writeInterest;
+    boolean readInterest;
+
+    // shutdownOutput has been called ?
+    private boolean outClosed = false;
+
+    // read() returned -1 OR input buffer closed ( no longer interested )
+    boolean inClosed = false;
+
+    // Saved to allow debug messages for bad interest/looping
+    int lastReadResult;
+    int zeroReads = 0;
+    int lastWriteResult;
+
+    protected NioChannel() {
+
+    }
+
+    public NioThread getSelectorThread() {
+        return sel;
+    }
+
+    public String toString() {
+        StringBuffer sb = new StringBuffer();
+        sb.append("SelData/")
+        .append(writeInterest ? "W/" : "")
+        .append(readInterest ? "R/" : "")
+        .append(outClosed ? "Out-CLOSE/" : "")
+        .append(inClosed ? "In-CLOSE/" : "")
+        .append("/")
+        .append(channel.toString());
+
+        return sb.toString();
+    }
+
+    public Channel getChannel() {
+        return channel;
+    }
+
+    public boolean isOpen() {
+        // in and out open
+        return channel.isOpen() && !outClosed && !inClosed;
+    }
+
+    public int read(ByteBuffer bb) throws IOException {
+        return sel.readNonBlocking(this, bb);
+    }
+
+    public int write(ByteBuffer bb) throws IOException {
+        return sel.writeNonBlocking(this, bb);
+    }
+
+    public void readInterest(boolean b) throws IOException {
+        sel.readInterest(this, b);
+    }
+
+    public void writeInterest() throws IOException {
+        sel.writeInterest(this);
+    }
+
+    public InetAddress getAddress(boolean remote) {
+        return sel.getAddress(this, remote);
+    }
+
+    public int getPort(boolean remote) {
+        return sel.getPort(this, remote);
+    }
+
+    /**
+     * Run in selector thread.
+     */
+    public void runInSelectorThread(Runnable t) throws IOException {
+        sel.runInSelectorThread(t);
+    }
+
+    /**
+     * Request a timer event. The thread will generate the events at
+     * a configurable interval - for example no more often than 0.5 sec.
+     */
+    public void setTimer(long timeMs, Runnable cb) {
+        this.nextTimeEvent = timeMs;
+        this.timeEvent = cb;
+    }
+
+    /**
+     *  shutdown out + in
+     *  If there is still data in the input buffer - RST will be sent
+     *  instead of FIN.
+     *
+     *
+     * The proper way to close a connection is to shutdownOutput() first,
+     * wait until read() return -1, then call close().
+     *
+     * If read() returns -1, you need to finish sending, call shutdownOutput()
+     * than close.
+     * If read() returns -1 and there is an error - call close()
+     * directly.
+     *
+     */
+    @Override
+    public void close() throws IOException {
+        shutdownOutput();
+        inputClosed();
+    }
+
+    /**
+     *  Send TCP close(FIN). HTTP uses this to transmit end of body. The other end
+     *  detects this with a '-1' in read().
+     *
+     *  All other forms of close() are reported as exceptions in read().
+     *
+     * @throws IOException
+     */
+    public void shutdownOutput() throws IOException {
+        synchronized (channel) {
+            if (!outClosed) {
+                outClosed = true;
+                try {
+                    sel.shutdownOutput(this);
+                } catch (IOException ex) {
+                    // ignore
+                }
+            }
+            if (inClosed) {
+                sel.close(this, null);
+            }
+        }
+    }
+
+    void inputClosed() throws IOException {
+        synchronized (channel) {
+            if (inClosed) {
+                // already closed
+                return;
+            }
+            inClosed = true; // detected end
+            if (outClosed) {
+                sel.close(this, null);
+            } else {
+                // Don't close the channel - write may still work ?
+                readInterest(false);
+            }
+        }
+    }
+
+    boolean closeCalled = false;
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java
new file mode 100644
index 0000000..9ec80be
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/NioThread.java
@@ -0,0 +1,1154 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.io;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.nio.ByteBuffer;
+import java.nio.channels.ByteChannel;
+import java.nio.channels.Channel;
+import java.nio.channels.ClosedChannelException;
+import java.nio.channels.SelectableChannel;
+import java.nio.channels.SelectionKey;
+import java.nio.channels.Selector;
+import java.nio.channels.ServerSocketChannel;
+import java.nio.channels.SocketChannel;
+import java.nio.channels.spi.SelectorProvider;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.io.NioChannel.NioChannelCallback;
+
+/**
+ * Abstract NIO/APR to avoid some of the complexity and allow more code
+ * sharing and experiments.
+ *
+ * SelectorThread provides non-blocking methods for read/write and generates
+ * callbacks using SelectorCallback. It has no buffers of its own.
+ *
+ * This is non-blocking, non-buffering and uses callbacks.
+ *
+ * @author Costin Manolache
+ */
+public class NioThread implements Runnable {
+
+  // ----------- IO handling -----------
+  protected long inactivityTimeout = 5000;
+  protected Thread selectorThread;
+
+
+  static Logger log = Logger.getLogger("NIO");
+
+  Selector selector;
+
+  // will be processed in the selector thread
+  List<NioChannel> readInterest = new ArrayList<NioChannel>();
+  List<NioChannel> writeInterest = new ArrayList<NioChannel>();
+  List<NioChannel> connectAcceptInterest = new ArrayList<NioChannel>();
+  List<NioChannel> updateCallback = new ArrayList<NioChannel>();
+  List<NioChannel> closeInterest = new LinkedList<NioChannel>();
+  List<Runnable> runnableInterest = new ArrayList<Runnable>();
+
+  // Statistics
+  AtomicInteger opened = new AtomicInteger();
+  AtomicInteger closed = new AtomicInteger();
+  AtomicInteger loops = new AtomicInteger();
+
+  AtomicInteger callbackCount = new AtomicInteger();
+  AtomicLong callbackTotalTime = new AtomicLong();
+  long maxCallbackTime = 0;
+
+  // actives are also stored in the Selector. This is only updated in the main
+  // thread
+  public ArrayList<NioChannel> active = new ArrayList<NioChannel>();
+
+  public static boolean debug = false;
+  boolean debugWakeup = false;
+  boolean running = true;
+
+  long lastWakeup = System.currentTimeMillis(); // last time we woke
+  long nextWakeup; // next scheduled wakeup
+
+  // Normally select will wait for the next time event - if it's
+  // too far in future, maxSleep will override it.
+  private long maxSleep = 600000;
+  long sleepTime = maxSleep;
+
+  // Never sleep less than minSleep. This defines the resulution for
+  // time events.
+  private long minSleep = 100;
+
+  boolean daemon = false;
+
+  // TODO: trace log - record all events with timestamps, replay
+
+  public NioThread(String name, boolean daemon) {
+      try {
+          selectorThread = (name == null) ? new Thread(this) :
+              new Thread(this, name);
+
+          selector = Selector.open();
+          // TODO: start it on-demand, close it when not in use
+          selectorThread.setDaemon(daemon);
+          this.daemon = daemon;
+
+          selectorThread.start();
+
+      } catch(IOException e) {
+          throw new RuntimeException(e);
+      }
+  }
+
+  /**
+   * Opened sockets, waiting for something ( close at least )
+   */
+  public int getOpen() {
+      return opened.get();
+  }
+
+  /**
+   * Closed - we're done with them.
+   */
+  public int getClosed() {
+      return closed.get();
+  }
+
+  public int getActive() {
+      return active.size();
+  }
+
+  public int getCallbacks() {
+      return callbackCount.get();
+  }
+
+  public long getMaxCallbackTime() {
+      return maxCallbackTime;
+  }
+
+  public long getAvgCallbackTime() {
+      int cnt = callbackCount.get();
+      if (cnt == 0) {
+          return 0;
+      }
+      return callbackTotalTime.get() / cnt;
+  }
+
+  /**
+   * How many times we looped
+   */
+  public int getLoops() {
+      return loops.get();
+  }
+
+  public long getLastWakeup() {
+      return lastWakeup;
+  }
+
+  public long getTimeSinceLastWakeup() {
+      return System.currentTimeMillis() - lastWakeup;
+  }
+
+  /**
+   * Close all resources, stop accepting, stop the thread.
+   * The actual stop will happen in background.
+   */
+  public void stop() {
+      running = false;
+      if (debug) {
+          log.info("Selector thread stop " + this);
+      }
+      selector.wakeup();
+  }
+
+  public void run() {
+      int sloops = 0;
+      if (debug) {
+          log.info("Start NIO thread, daemon=" + daemon);
+      }
+      while (running) {
+          // if we want timeouts - set here.
+          try {
+              loops.incrementAndGet();
+
+              // Check if new requests were added
+              processPending();
+
+              // Timers
+              long now = System.currentTimeMillis();
+              if (nextWakeup < now) {
+                  // We don't want to iterate on every I/O
+                  updateSleepTimeAndProcessTimeouts(now);
+              }
+
+              int selected = selector.select(sleepTime);
+
+              lastWakeup = System.currentTimeMillis();
+              long slept = lastWakeup - now;
+
+              if (debugWakeup && selected == 0) {
+                  if (sleepTime < maxSleep - 1000) { // short wakeup
+                      log.info("Wakeup " + selected + " " + slept
+                              + " " + sleepTime);
+                  }
+              }
+              if (slept < 10 && selected == 0) {
+                  if (sloops > 50) {
+                      sloops = 0;
+                      log.severe("Looping !");
+                      resetSelector();
+                  }
+                  sloops++;
+              }
+
+              // handle events for existing req first.
+              if (selected != 0) {
+                  sloops = 0;
+                  int callbackCnt = 0;
+                  Set<SelectionKey> sel = selector.selectedKeys();
+                  Iterator<SelectionKey> i = sel.iterator();
+
+                  while (i.hasNext()) {
+                      callbackCnt++;
+                      long beforeCallback = System.currentTimeMillis();
+                      SelectionKey sk = i.next();
+                      i.remove();
+
+                      boolean valid = sk.isValid();
+                      int readyOps = (valid) ? sk.readyOps() : 0;
+
+                      NioChannel ch = (NioChannel) sk.attachment();
+                      if (debugWakeup) {
+                          log.info("Wakeup selCnt=" + selected + " slept=" + (lastWakeup - now) +
+                                  " ready: " + readyOps + " v=" +
+                                  sk.isValid() + " ch=" + ch);
+                      }
+                      if (ch == null) {
+                          log.severe("Missing channel");
+                          sk.cancel();
+                          continue;
+                      }
+                      if (ch.selKey != sk) {
+                          // if (ch.selKey != null) { // null if closed
+                          log.severe("Invalid state, selKey doesn't match ");
+                          ch.selKey = sk;
+                      }
+                      if (ch.channel != sk.channel()) {
+                          ch.channel = sk.channel();
+                          log.severe("Invalid state, channel doesn't match ");
+                      }
+
+                      if (!sk.isValid()) {
+                          if (debug) {
+                              log.info("!isValid, closed socket " + ch);
+                          }
+                          ch.close();
+                          continue;
+                      }
+
+                      try {
+                          int ready = sk.readyOps();
+                          // callbacks
+                          if (sk.isValid() && sk.isAcceptable()) {
+                              handleAccept(ch, sk);
+                          }
+
+                          if (sk.isValid() && sk.isConnectable()) {
+                              sk.interestOps(sk.interestOps() & ~SelectionKey.OP_CONNECT);
+                              SocketChannel sc = (SocketChannel) sk.channel();
+                              handleConnect(ch, sc);
+                          }
+
+                          if (sk.isValid() && sk.isWritable()) {
+                              // Needs to be explicitely re-enabled by callback
+                              // if more data.
+                              sk.interestOps(sk.interestOps() & ~SelectionKey.OP_WRITE);
+                              ch.writeInterest = false;
+                              handleDataWriteable(ch);
+                          }
+
+                          if (sk.isValid() && sk.isReadable()) {
+                              // Leave readable interest !
+                              handleReadable(ch);
+                          }
+
+                          long callbackTime =
+                              System.currentTimeMillis() - beforeCallback;
+
+                          if (callbackTime > 250) {
+                              log.warning("Callback too long ! ops=" + ready +
+                                      " time=" + callbackTime + " ch=" + ch +
+                                      " " + callbackCnt);
+                          }
+                          if (callbackTime > maxCallbackTime) {
+                              maxCallbackTime = callbackTime;
+                          }
+                          callbackCount.incrementAndGet();
+                          this.callbackTotalTime.addAndGet(callbackTime);
+
+                      } catch (Throwable t) {
+                          log.log(Level.SEVERE, "SelectorThread: Channel error, closing", t);
+                          ch.lastException = t;
+                          ch.close();
+                      }
+
+                  }
+                  // All at once
+                  sel.clear();
+              }
+
+          } catch (Throwable e) {
+              log.log(Level.SEVERE, "SelectorThread: Error in select", e);
+          }
+      } // while(running)
+      log.info("SelectorThread done");
+  }
+
+  private void log(String msg, int selected, long slept, SelectionKey sk, int readyOps) {
+      log.info(msg + " " + selected
+              + " " + slept
+              + " ready: " + readyOps + " "
+              + sk.readyOps() + " " + sk);
+  }
+
+  private void resetSelector() throws IOException, ClosedChannelException {
+      // Let's close all sockets - one is bad, but we can't do much.
+      Set<SelectionKey> keys = selector.keys();
+      //Set<SelectionKey> keys = selector.keys();
+      ArrayList<NioChannel> oldCh = new ArrayList<NioChannel>();
+      ArrayList<Integer> interests = new ArrayList<Integer>();
+      for (SelectionKey k : keys) {
+          NioChannel cd = (NioChannel) k.attachment();
+          interests.add(k.interestOps());
+          oldCh.add(cd);
+          k.cancel();
+      }
+
+      selector.close();
+      selector = Selector.open();
+      for (int i = 0; i < oldCh.size(); i++) {
+          NioChannel selectorData = oldCh.get(i);
+          if (selectorData == null) {
+              continue;
+          }
+          int interest = interests.get(i);
+          if (selectorData.channel instanceof ServerSocketChannel) {
+              ServerSocketChannel socketChannel =
+                  (ServerSocketChannel) selectorData.channel;
+              selectorData.selKey = socketChannel.register(selector, SelectionKey.OP_ACCEPT);
+          } else {
+              SocketChannel socketChannel =
+                  (SocketChannel) selectorData.channel;
+              if (interest != 0) {
+                  selectorData.selKey = socketChannel.register(selector,
+                      interest);
+              }
+
+          }
+      }
+  }
+
+  private void handleReadable(NioChannel ch) throws IOException {
+      ch.lastReadResult = 0;
+      if (ch.callback != null) {
+          ch.callback.handleReadable(ch);
+      }
+      if (ch.lastReadResult != 0 && ch.readInterest && !ch.inClosed) {
+          log.warning("LOOP: read interest" +
+                      " after incomplete read");
+          ch.close();
+      }
+  }
+
+  private void handleDataWriteable(NioChannel ch) throws IOException {
+      ch.lastWriteResult = 0;
+      if (ch.callback != null) {
+          ch.callback.handleWriteable(ch);
+      }
+      if (ch.lastWriteResult > 0 && ch.writeInterest) {
+          log.warning("SelectorThread: write interest" +
+                      " after incomplete write, LOOP");
+      }
+  }
+
+  private void handleConnect(NioChannel ch, SocketChannel sc)
+          throws IOException, SocketException {
+      try {
+          if (!sc.finishConnect()) {
+              log.warning("handleConnected - finishConnect returns false");
+          }
+          ch.sel = this;
+          //sc.socket().setSoLinger(true, 0);
+          if (debug) {
+              log.info("connected() " + ch + " isConnected()=" + sc.isConnected() + " " +
+                      sc.isConnectionPending());
+          }
+
+          readInterest(ch, true);
+      } catch (Throwable t) {
+          close(ch, t);
+      }
+      try {
+          if (ch.callback != null) {
+              ch.callback.handleConnected(ch);
+          }
+      } catch(Throwable t1) {
+          log.log(Level.WARNING, "Error in connect callback", t1);
+      }
+
+  }
+
+  private void handleAccept(NioChannel ch, SelectionKey sk)
+          throws IOException, ClosedChannelException {
+      SelectableChannel selc = sk.channel();
+      ServerSocketChannel ssc=(ServerSocketChannel)selc;
+      SocketChannel sockC = ssc.accept();
+      sockC.configureBlocking(false);
+
+      NioChannel acceptedChannel = new NioChannel(this);
+      acceptedChannel.selKey = sockC.register(selector,
+              SelectionKey.OP_READ,
+              acceptedChannel);
+      acceptedChannel.channel = sockC;
+
+      synchronized (active) {
+          active.add(acceptedChannel);
+      }
+
+      // Find the callback for the new socket
+      if (ch.callback != null) {
+          // TODO: use future !
+          try {
+              ch.callback.handleConnected(acceptedChannel);
+          } catch (Throwable t) {
+              log.log(Level.SEVERE, "SelectorThread: Channel error, closing ", t);
+              acceptedChannel.lastException = t;
+              acceptedChannel.close();
+          }
+     }
+
+      //sk.interestOps(sk.interestOps() | SelectionKey.OP_ACCEPT);
+      if (debug) {
+          log.info("handleAccept " + ch);
+      }
+  }
+
+
+  public void shutdownOutput(NioChannel ch) throws IOException {
+      Channel channel = ch.channel;
+      if (channel instanceof SocketChannel) {
+          SocketChannel sc = (SocketChannel) channel;
+          if (sc.isOpen() && sc.isConnected()) {
+              if (debug) {
+                  log.info("Half shutdown " + ch);
+              }
+              sc.socket().shutdownOutput(); // TCP end to the other side
+          }
+      }
+  }
+
+  /**
+   * Called from the IO thread
+   */
+  private void closeIOThread(NioChannel ch, boolean remove) {
+      SelectionKey sk = (SelectionKey) ch.selKey;
+      Channel channel = ch.channel;
+      try {
+          synchronized(closeInterest) {
+              if (ch.closeCalled) {
+                  if (debug) {
+                      log.severe("Close called 2x ");
+                  }
+                  return;
+              }
+              ch.closeCalled = true;
+              int o = opened.decrementAndGet();
+              if (debug) {
+                  log.info("-------------> close: " + ch + " t=" + ch.lastException);
+              }
+              if (sk != null) {
+                  if (sk.isValid()) {
+                      sk.interestOps(0);
+                  }
+                  sk.cancel();
+                  ch.selKey = null;
+              }
+
+              if (channel instanceof SocketChannel) {
+                  SocketChannel sc = (SocketChannel) channel;
+
+                  if (sc.isConnected()) {
+                      if (debug) {
+                          log.info("Close socket, opened=" + o);
+                      }
+                      try {
+                          sc.socket().shutdownInput();
+                      } catch(IOException io1) {
+                      }
+                      try {
+                          sc.socket().shutdownOutput(); // TCP end to the other side
+                      } catch(IOException io1) {
+                      }
+                      sc.socket().close();
+                  }
+              }
+              channel.close();
+
+              closed.incrementAndGet();
+
+              if (ch.callback != null) {
+                  ch.callback.handleClosed(ch);
+              }
+              // remove from active - false only if already removed
+              if (remove) {
+                  synchronized (active) {
+                      boolean removed = active.remove(ch);
+                  }
+              }
+      }
+      } catch (IOException ex2) {
+          log.log(Level.SEVERE, "SelectorThread: Error closing socket ", ex2);
+      }
+  }
+
+  // --------------- Socket op abstractions ------------
+
+  public int readNonBlocking(NioChannel selectorData, ByteBuffer bb)
+  throws IOException {
+      try {
+          int off = bb.position();
+
+          int done = 0;
+
+          done = ((SocketChannel) selectorData.channel).read(bb);
+
+          if (debug) {
+              log.info("-------------readNB rd=" + done + " bb.limit=" +
+                      bb.limit() + " pos=" + bb.position() + " " + selectorData);
+          }
+          if (done > 0) {
+              if (debug) {
+                  if (!bb.isDirect()) {
+                      String s = new String(bb.array(), off,
+                          bb.position() - off);
+                      log.info("Data:\n" + s);
+                  } else {
+                      log.info("Data: " + bb.toString());
+                  }
+              }
+              selectorData.zeroReads = 0;
+          } else if (done < 0) {
+              if (debug) {
+                  log.info("SelectorThread: EOF while reading " + selectorData);
+              }
+          } else {
+              // need more...
+              if (selectorData.lastReadResult == 0) {
+                  selectorData.zeroReads++;
+                  if (selectorData.zeroReads > 6) {
+                      log.severe("LOOP 0 reading ");
+                      selectorData.lastException = new IOException("Polling read");
+                      selectorData.close();
+                      return -1;
+                  }
+              }
+          }
+          selectorData.lastReadResult = done;
+          return done;
+      } catch(IOException ex) {
+          if (debug) {
+              log.info("readNB error rd=" + -1 + " bblen=" +
+                      (bb.limit() - bb.position()) + " " + selectorData + " " + ex);
+          }
+          // common case: other side closed the connection. No need for trace
+          if (ex.toString().indexOf("Connection reset by peer") < 0) {
+              ex.printStackTrace();
+          }
+          selectorData.lastException = ex;
+          selectorData.close();
+          return -1;
+      }
+  }
+
+  /**
+   *  May be called from any thread
+   */
+  public int writeNonBlocking(NioChannel selectorData, ByteBuffer bb)
+          throws IOException {
+      try {
+          if (debug) {
+              log.info("writeNB pos=" + bb.position() + " len=" +
+                      (bb.limit() - bb.position()) + " " + selectorData);
+             if (!bb.isDirect()) {
+                  String s = new String(bb.array(), bb.position(),
+
+                      bb.limit() - bb.position());
+                  log.info("Data:\n" + s);
+              }
+          }
+          if (selectorData.writeInterest) {
+              // writeInterest will be false after a callback, if it is
+              // set it means we want to wait for the callback.
+              if (debug) {
+                  log.info("Prevent writeNB when writeInterest is set");
+              }
+              return 0;
+          }
+
+          int done = 0;
+          done = ((SocketChannel) selectorData.channel).write(bb);
+          selectorData.lastWriteResult = done;
+          return done;
+      } catch(IOException ex) {
+          if (debug) {
+              log.info("writeNB error pos=" + bb.position() + " len=" +
+                      (bb.limit() - bb.position()) + " " + selectorData + " " +
+                      ex);
+          }
+          //ex.printStackTrace();
+          selectorData.lastException = ex;
+          selectorData.close();
+          throw ex;
+          // return -1;
+      }
+  }
+
+  public int getPort(NioChannel sd, boolean remote) {
+      SocketChannel socketChannel = (SocketChannel) sd.channel;
+
+      if (remote) {
+          return socketChannel.socket().getPort();
+      } else {
+          return socketChannel.socket().getLocalPort();
+      }
+  }
+
+  public InetAddress getAddress(NioChannel sd, boolean remote) {
+      SocketChannel socketChannel = (SocketChannel) sd.channel;
+
+      if (remote) {
+          return socketChannel.socket().getInetAddress();
+      } else {
+          return socketChannel.socket().getLocalAddress();
+      }
+  }
+
+  /**
+   */
+  public void connect(String host, int port, NioChannelCallback cstate)
+          throws IOException {
+      connect(new InetSocketAddress(host, port), cstate);
+  }
+
+
+  public void connect(SocketAddress sa, NioChannelCallback cstate)
+          throws IOException {
+      connect(sa, cstate, null);
+  }
+
+  public void connect(SocketAddress sa, NioChannelCallback cstate,
+                      NioChannel filter)
+          throws IOException {
+
+      SocketChannel socketChannel = SocketChannel.open();
+      socketChannel.configureBlocking(false);
+      NioChannel selectorData = new NioChannel(this);
+      selectorData.sel = this;
+      selectorData.callback = cstate;
+      selectorData.channel = socketChannel;
+      selectorData.channel = socketChannel; // no key
+
+      socketChannel.connect(sa);
+      opened.incrementAndGet();
+
+      synchronized (connectAcceptInterest) {
+          connectAcceptInterest.add(selectorData);
+      }
+      selector.wakeup();
+  }
+
+  // TODO
+  public void configureSocket(ByteChannel ch,
+                              boolean noDelay) throws IOException {
+      SocketChannel sockC = (SocketChannel) ch;
+      sockC.socket().setTcpNoDelay(noDelay);
+  }
+
+  // TODO
+  public void setSocketOptions(NioChannel selectorData,
+                               int linger,
+                               boolean tcpNoDelay,
+                               int socketTimeout)
+  throws IOException {
+
+      SocketChannel socketChannel =
+          (SocketChannel) selectorData.channel;
+      Socket socket = socketChannel.socket();
+
+      if(linger >= 0 )
+          socket.setSoLinger( true, linger);
+      if( tcpNoDelay )
+          socket.setTcpNoDelay(tcpNoDelay);
+      if( socketTimeout > 0 )
+          socket.setSoTimeout( socketTimeout );
+  }
+
+  /**
+   * Can be called from multiple threads or multiple times.
+   */
+  public int close(NioChannel selectorData, Throwable exception) throws IOException {
+      synchronized (closeInterest) {
+          if (exception != null) {
+              selectorData.lastException = exception;
+          }
+          selectorData.readInterest = false;
+          if (isSelectorThread()) {
+              closeIOThread(selectorData, true);
+              return 0;
+          }
+          if (!selectorData.inClosed) {
+              closeInterest.add(selectorData);
+          }
+      }
+      selector.wakeup();
+      return 0;
+  }
+
+
+  public void acceptor(NioChannelCallback cstate,
+                       int port,
+                       InetAddress inet,
+                       int backlog,
+                       int serverTimeout)
+  throws IOException
+  {
+      ServerSocketChannel ssc=ServerSocketChannel.open();
+      ServerSocket serverSocket = ssc.socket();
+
+      SocketAddress sa = null;
+
+      if (inet == null) {
+          sa = new InetSocketAddress( port );
+      } else {
+          sa = new InetSocketAddress(inet, port);
+      }
+      if (backlog > 0) {
+          serverSocket.bind( sa , backlog);
+      } else {
+          serverSocket.bind(sa);
+      }
+      if( serverTimeout >= 0 ) {
+          serverSocket.setSoTimeout( serverTimeout );
+      }
+
+
+      ssc.configureBlocking(false);
+
+      NioChannel selectorData = new NioChannel(this);
+      selectorData.channel = ssc; // no key yet
+      selectorData.callback = cstate;
+      // key will be set in pending
+
+      // TODO: add SSL here
+
+      synchronized (connectAcceptInterest) {
+          connectAcceptInterest.add(selectorData);
+      }
+      selector.wakeup();
+  }
+
+  public void runInSelectorThread(Runnable cb) throws IOException {
+      if (isSelectorThread()) {
+          cb.run();
+      } else {
+          synchronized (runnableInterest) {
+              runnableInterest.add(cb);
+          }
+          selector.wakeup();
+      }
+  }
+
+  /**
+   * Example config:
+   *
+   * www stream tcp wait USER  PATH_TO_tomcatInetd.sh
+   *
+   * For a different port, you need to add it to /etc/services.
+   *
+   * 'wait' is critical - the common use of inetd is 'nowait' for
+   * tcp services, which doesn't make sense for java ( too slow startup
+   * time ). It may make sense in future with something like android VM.
+   *
+   * In 'wait' mode, inetd will pass the acceptor socket to java - so
+   * you can listen on port 80 and run as regular user with no special
+   * code and magic.
+   * If tomcat dies, inetd will get back the acceptor and on next connection
+   * restart tomcat.
+   *
+   * This also works with xinetd. It might work with Apple launchd.
+   *
+   * TODO: detect inactivity for N minutes, exist - to free resources.
+   */
+  public void inetdAcceptor(NioChannelCallback cstate) throws IOException {
+      SelectorProvider sp=SelectorProvider.provider();
+
+      Channel ch=sp.inheritedChannel();
+      if(ch!=null ) {
+          log.info("Inherited: " + ch.getClass().getName());
+          // blocking mode
+          ServerSocketChannel ssc=(ServerSocketChannel)ch;
+          ssc.configureBlocking(false);
+
+          NioChannel selectorData = new NioChannel(this);
+          selectorData.channel = ssc;
+          selectorData.callback = cstate;
+
+          synchronized (connectAcceptInterest) {
+              connectAcceptInterest.add(selectorData);
+          }
+          selector.wakeup();
+      } else {
+          log.severe("No inet socket ");
+          throw new IOException("Invalid inheritedChannel");
+      }
+  }
+
+  // -------------- Housekeeping -------------
+  /**
+   *  Same as APR connector - iterate over tasks, get
+   *  smallest timeout
+   * @throws IOException
+   */
+  void updateSleepTimeAndProcessTimeouts(long now)
+          throws IOException {
+      long min = Long.MAX_VALUE;
+      // TODO: test with large sets, maybe sort
+      synchronized (active) {
+          Iterator<NioChannel> activeIt = active.iterator();
+
+          while(activeIt.hasNext()) {
+              NioChannel selectorData = activeIt.next();
+              if (! selectorData.channel.isOpen()) {
+                  if (debug) {
+                      log.info("Found closed socket, removing " +
+                              selectorData.channel);
+                  }
+//                  activeIt.remove();
+//                  selectorData.close();
+              }
+
+              long t = selectorData.nextTimeEvent;
+              if (t == 0) {
+                  continue;
+              }
+              if (t < now) {
+                  // Timeout
+                  if (debug) {
+                      log.info("Time event " + selectorData);
+                  }
+                  if (selectorData.timeEvent != null) {
+                      selectorData.timeEvent.run();
+                  }
+                  // TODO: make sure this is updated if it was selected
+                  continue;
+              }
+              if (t < min) {
+                  min = t;
+              }
+          }
+      }
+      long nextSleep = min - now;
+      if (nextSleep > maxSleep) {
+          sleepTime = maxSleep;
+      } else if (nextSleep < minSleep) {
+          sleepTime = minSleep;
+      } else {
+          sleepTime = nextSleep;
+      }
+      nextWakeup = now + sleepTime;
+  }
+
+  /**
+   * Request a callback whenever data can be written.
+   * When the callback is invoked, the write interest is removed ( to avoid
+   * looping ). If the write() operation doesn't complete, you must call
+   * writeInterest - AND stop writing, some implementations will throw
+   * exception. write() will actually attempt to detect this and avoid the
+   * error.
+   *
+   * @param sc
+   */
+  public void writeInterest(NioChannel selectorData) {
+      // TODO: suspended ?
+
+      SelectionKey sk = (SelectionKey) selectorData.selKey;
+      if (!sk.isValid()) {
+          return;
+      }
+      selectorData.writeInterest = true;
+      int interest = sk.interestOps();
+      if ((interest & SelectionKey.OP_WRITE) != 0) {
+          return;
+      }
+      if (Thread.currentThread() == selectorThread) {
+          interest =
+              interest | SelectionKey.OP_WRITE;
+          sk.interestOps(interest);
+          if (debug) {
+              log.info("Write interest " + selectorData + " i=" + interest);
+          }
+          return;
+      }
+      if (debug) {
+          log.info("Pending write interest " + selectorData);
+      }
+      synchronized (writeInterest) {
+          writeInterest.add(selectorData);
+      }
+      selector.wakeup();
+  }
+
+
+  public void readInterest(NioChannel selectorData, boolean b) throws IOException {
+      if (Thread.currentThread() == selectorThread) {
+          selectorData.readInterest = b;
+          selThreadReadInterest(selectorData);
+          return;
+      }
+      SelectionKey sk = (SelectionKey) selectorData.selKey;
+      if (sk == null) {
+          close(selectorData, null);
+          return;
+      }
+      int interest = sk.interestOps();
+      selectorData.readInterest = b;
+      if (b && (interest & SelectionKey.OP_READ) != 0) {
+          return;
+      }
+      if (!b && (interest & SelectionKey.OP_READ) == 0) {
+          return;
+      }
+      // Schedule the interest update.
+      synchronized (readInterest) {
+          readInterest.add(selectorData);
+      }
+      if (debug) {
+          log.info("Registering pending read interest");
+      }
+      selector.wakeup();
+  }
+
+
+  private void selThreadReadInterest(NioChannel selectorData) throws IOException {
+      SelectionKey sk = (SelectionKey) selectorData.selKey;
+      if (sk == null) {
+          if (selectorData.readInterest) {
+              if (debug) {
+                  log.info("Register again for read interest");
+              }
+              SocketChannel socketChannel =
+                  (SocketChannel) selectorData.channel;
+              if (socketChannel.isOpen()) {
+                  selectorData.sel = this;
+                  selectorData.selKey =
+                      socketChannel.register(selector,
+                              SelectionKey.OP_READ, selectorData);
+                  selectorData.channel = socketChannel;
+              }
+          }
+          return;
+      }
+      if (!sk.isValid()) {
+          return;
+      }
+      int interest = sk.interestOps();
+      if (sk != null && sk.isValid()) {
+          if (selectorData.readInterest) {
+//              if ((interest | SelectionKey.OP_READ) != 0) {
+//                  return;
+//              }
+              interest =
+                  interest | SelectionKey.OP_READ;
+          } else {
+//              if ((interest | SelectionKey.OP_READ) == 0) {
+//                  return;
+//              }
+              interest =
+                  interest & ~SelectionKey.OP_READ;
+          }
+          if (interest == 0) {
+              if (!selectorData.inClosed) {
+                  new Throwable().printStackTrace();
+                  log.warning("No interest(rd removed) " + selectorData);
+              }
+              // TODO: should we remove it ? It needs to be re-activated
+              // later.
+              sk.cancel(); //??
+              selectorData.selKey = null;
+          } else {
+              sk.interestOps(interest);
+          }
+          if (debug) {
+              log.info(((selectorData.readInterest)
+                      ? "RESUME read " : "SUSPEND read ")
+                      + selectorData);
+          }
+      }
+  }
+
+
+  private void processPendingConnectAccept() throws IOException {
+      synchronized (connectAcceptInterest) {
+          Iterator<NioChannel> ci = connectAcceptInterest.iterator();
+
+          while (ci.hasNext()) {
+              NioChannel selectorData = ci.next();
+
+              // Find host, port - initiate connection
+              try {
+                  // Accept interest ?
+                  if (selectorData.channel instanceof ServerSocketChannel) {
+                      ServerSocketChannel socketChannel =
+                          (ServerSocketChannel) selectorData.channel;
+                      selectorData.sel = this;
+                      selectorData.selKey =
+                        socketChannel.register(selector,
+                            SelectionKey.OP_ACCEPT, selectorData);
+
+                      selectorData.channel = socketChannel;
+                      synchronized (active) {
+                          active.add(selectorData);
+                      }
+                      if (debug) {
+                          log.info("Pending acceptor added: " + selectorData);
+                      }
+                  } else {
+                      SocketChannel socketChannel =
+                          (SocketChannel) selectorData.channel;
+                      selectorData.sel = this;
+                      selectorData.selKey =
+                        socketChannel.register(selector,
+                            SelectionKey.OP_CONNECT, selectorData);
+                      synchronized (active) {
+                          active.add(selectorData);
+                      }
+                      if (debug) {
+                          log.info("Pending connect added: " + selectorData);
+                      }
+                  }
+              } catch (Throwable e) {
+                  log.log(Level.SEVERE, "error registering connect/accept",
+                          e);
+              }
+          }
+          connectAcceptInterest.clear();
+      }
+  }
+
+  private void processPending() throws IOException {
+      if (closeInterest.size() > 0) {
+          synchronized (closeInterest) {
+              List<NioChannel> closeList = new ArrayList(closeInterest);
+              closeInterest.clear();
+
+              Iterator<NioChannel> ci = closeList.iterator();
+
+              while (ci.hasNext()) {
+                  try {
+                      NioChannel selectorData = ci.next();
+                      closeIOThread(selectorData, true);
+                  } catch (Throwable t) {
+                      t.printStackTrace();
+                  }
+              }
+          }
+      }
+      processPendingConnectAccept();
+      processPendingReadWrite();
+
+      if (runnableInterest.size() > 0) {
+          synchronized (runnableInterest) {
+              Iterator<Runnable> ci = runnableInterest.iterator();
+              while (ci.hasNext()) {
+                  Runnable cstate = ci.next();
+                  try {
+                      cstate.run();
+                  } catch (Throwable t) {
+                      t.printStackTrace();
+                  }
+                  if (debug) {
+                      log.info("Run in selthread: " + cstate);
+                  }
+              }
+              runnableInterest.clear();
+          }
+      }
+      //processPendingUpdateCallback();
+  }
+
+  private void processPendingReadWrite() throws IOException {
+      // Update interest
+      if (readInterest.size() > 0) {
+          synchronized (readInterest) {
+              Iterator<NioChannel> ci = readInterest.iterator();
+              while (ci.hasNext()) {
+                  NioChannel cstate = ci.next();
+                  selThreadReadInterest(cstate);
+                  if (debug) {
+                      log.info("Read interest added: " + cstate);
+                  }
+              }
+              readInterest.clear();
+          }
+      }
+      if (writeInterest.size() > 0) {
+          synchronized (writeInterest) {
+              Iterator<NioChannel> ci = writeInterest.iterator();
+              while (ci.hasNext()) {
+                  NioChannel cstate = ci.next();
+                  // Fake callback - will update as side effect
+                  handleDataWriteable(cstate);
+                  if (debug) {
+                      log.info("Write interest, calling dataWritable: " + cstate);
+                  }
+              }
+              writeInterest.clear();
+          }
+      }
+  }
+
+
+  protected boolean isSelectorThread() {
+      return Thread.currentThread() == selectorThread;
+  }
+
+  public static boolean isSelectorThread(IOChannel ch) {
+      SocketIOChannel sc = (SocketIOChannel) ch.getFirst();
+      return Thread.currentThread() == sc.ch.sel.selectorThread;
+  }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java
new file mode 100644
index 0000000..283b947
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketConnector.java
@@ -0,0 +1,132 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.net.InetSocketAddress;
+import java.util.Timer;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.io.NioChannel.NioChannelCallback;
+
+/**
+ * Class for handling sockets. It manages a pool of SelectorThreads, fully
+ * non-blocking. There is no caching or buffer management. SelectorChannel
+ * represents on connection.
+ *
+ * In the old types, the connector was socket-centric, and quite ugly. After
+ * many refactoring the buffers ( buckets and brigade ) and callbacks are
+ * used everywhere, and the sockets play a supporting role.
+ *
+ * TODO: discover if APR is available and use it, or fall back to NIO.
+ *
+ * @author Costin Manolache
+ */
+public class SocketConnector extends IOConnector {
+    static Logger log = Logger.getLogger(SocketConnector.class.getName());
+    static boolean debug = false;
+
+    // TODO: pool, balanced usage
+    // TODO: bind into OM or callback when created
+
+    private NioThread selector;
+
+    // For resolving DNS ( i.e. connect )
+    Executor threadPool = Executors.newCachedThreadPool();
+
+    public SocketConnector() {
+        timer = new Timer(true);
+    }
+
+    public SocketConnector(int port) {
+        timer = new Timer(true);
+    }
+
+    /**
+     * This may be blocking - involves host resolution, connect.
+     * If the IP address is provided - it shouldn't block.
+     */
+    @Override
+    public void connect(final String host, final int port,
+                             final IOConnector.ConnectedCallback sc) throws IOException {
+        final SocketIOChannel ioch = new SocketIOChannel(this, null, host + ":" + port);
+        ioch.setConnectedCallback(sc);
+        threadPool.execute(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    getSelector().connect(new InetSocketAddress(host, port), ioch, null);
+                } catch (Throwable e) {
+                    e.printStackTrace();
+                    try {
+                        sc.handleConnected(ioch);
+                        ioch.close();
+                    } catch (Throwable e1) {
+                        e1.printStackTrace();
+                    }
+                }
+            }
+        });
+    }
+
+    /**
+     * Create a new server socket, register the callback.
+     * If port == 0 it'll use the inherited channel, i.e. inetd mode.
+     * TODO: if port == -1, detect a free port. May block.
+     */
+    public void acceptor(final IOConnector.ConnectedCallback sc,
+                         final CharSequence address, Object extra)
+        throws IOException
+    {
+        final int port = Integer.parseInt(address.toString());
+        NioChannelCallback acceptCb = new NioChannelCallback() {
+            @Override
+            public void handleClosed(NioChannel ch) throws IOException {
+            }
+
+            @Override
+            public void handleConnected(NioChannel ch) throws IOException {
+                SocketIOChannel ioch = new SocketIOChannel(SocketConnector.this,
+                        ch, ":" + port);
+                sc.handleConnected(ioch);
+            }
+
+            @Override
+            public void handleReadable(NioChannel ch) throws IOException {
+            }
+
+            @Override
+            public void handleWriteable(NioChannel ch) throws IOException {
+            }
+        };
+
+        if (port == -1) {
+            // TODO: find an unused port
+        } else if (port == 0) {
+            getSelector().inetdAcceptor(acceptCb);
+        }  else {
+            getSelector().acceptor(acceptCb, port, null, 200, 20000);
+        }
+    }
+
+    static int id = 0;
+
+    public synchronized NioThread getSelector() {
+        if (selector == null) {
+            String name = "SelectorThread-" + id++;
+            selector = new NioThread(name, true);
+        }
+
+        return selector;
+    }
+
+    public void stop() {
+        getSelector().stop();
+    }
+
+
+    // TODO: suspendAccept(boolean)
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java
new file mode 100644
index 0000000..c74ccc5
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SocketIOChannel.java
@@ -0,0 +1,271 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.nio.ByteBuffer;
+
+import org.apache.tomcat.lite.io.NioChannel.NioChannelCallback;
+
+/**
+ * Buffered socket channel
+ */
+public class SocketIOChannel extends IOChannel implements NioChannelCallback {
+    IOBuffer out;
+    IOBuffer in;
+
+    NioChannel ch;
+
+    SocketIOChannel(IOConnector connector, NioChannel data,
+            String target)
+            throws IOException {
+        this.connector = connector;
+        in = new IOBuffer(this);
+        out = new IOBuffer(this);
+        this.ch = data;
+        setOutBuffer(out);
+        setChannel(data);
+        this.target = target;
+    }
+
+    void setChannel(NioChannel data) {
+        this.ch = data;
+        if (ch != null) {
+            ch.callback = this;
+        }
+    }
+
+
+    @Override
+    public IOBuffer getIn() {
+        return in;
+    }
+
+    @Override
+    public IOBuffer getOut() {
+        return out;
+    }
+
+    /**
+     * Both in and out open
+     */
+    public boolean isOpen() {
+        if (ch == null) {
+            return false;
+        }
+        return ch.isOpen() && ch.channel != null &&
+            ch.channel.isOpen() && !getIn().isAppendClosed() &&
+            !getOut().isAppendClosed();
+    }
+
+    NioChannel getSelectorChannel() {
+        return ch;
+    }
+
+    public String toString() {
+        return ch.toString();
+    }
+
+    public void setOutBuffer(IOBuffer out) {
+        this.out = out;
+    }
+
+    ByteBuffer flushBuffer;
+
+    /**
+     * Send as much as possible.
+     *
+     * Adjust write interest so we can send more when possible.
+     */
+    private void flush(NioChannel ch) throws IOException {
+        synchronized (this) {
+            if (ch == null) {
+                if (out.isClosedAndEmpty()) {
+                    return;
+                }
+                throw new IOException("flush() with closed socket");
+            }
+            while (true) {
+                if (out.isClosedAndEmpty()) {
+                    ch.shutdownOutput();
+                    break;
+                }
+                BBucket bb = out.peekFirst();
+                if (bb == null) {
+                    break;
+                }
+                flushBuffer = getReadableBuffer(flushBuffer, bb);
+                int before = flushBuffer.position();
+
+                int done = 0;
+                while (flushBuffer.remaining() > 0) {
+                    try {
+                        done = ch.write(flushBuffer);
+                    } catch (IOException ex) {
+                        // can't write - was closed !
+                        done = -1;
+                    }
+
+                    if (done < 0) {
+                        ch.close();
+                        out.close();
+                        handleFlushed(this);
+                        //throw new IOException("Closed while writting ");
+                        return;
+                    }
+                    if (done == 0) {
+                        bb.position(flushBuffer.position());
+                        ch.writeInterest(); // it is cleared on next dataWriteable
+                        return;
+                    }
+                }
+                releaseReadableBuffer(flushBuffer, bb);
+            }
+            handleFlushed(this);
+
+        }
+    }
+
+    /**
+     * Data available for read, called from IO thread.
+     * You MUST read all data ( i.e. until read() returns 0).
+     *
+     * OP_READ remain active - call readInterest(false) to disable -
+     * for example to suspend reading if buffer is full.
+     */
+    public void handleReceived(IOChannel net) throws IOException {
+        // All data will go to currentReceiveBuffer, until it's full.
+        // Then a new buffer will be allocated/pooled.
+
+        // When we fill the buffers or finish this round of reading -
+        // we place the Buckets in the queue, as 'readable' buffers.
+        boolean newData = false;
+        try {
+            int read = 0;
+            synchronized(in) {
+                // data between 0 and position
+                int total = 0;
+                while (true) {
+                    if (in.isAppendClosed()) { // someone closed me ?
+                        ch.inputClosed(); // remove read interest.
+                        // if outClosed - close completely
+                        newData = true;
+                        break;
+                    }
+
+                    ByteBuffer bb = in.getWriteBuffer();
+                    read = ch.read(bb);
+                    in.releaseWriteBuffer(read);
+
+                    if (in == null) { // Detached.
+                        break;
+                    }
+
+                    if (read < 0) {
+                        // mark the in buffer as closed
+                        in.close();
+                        ch.inputClosed();
+                        newData = true;
+                        break;
+                    }
+                    if (read == 0) {
+                        break;
+                    }
+                    total += read;
+                    newData = true;
+                }
+            } // sync
+            if (newData) {
+                super.sendHandleReceivedCallback();
+            }
+
+        } catch (Throwable t) {
+            close();
+            if (t instanceof IOException) {
+                throw (IOException) t;
+            } else {
+                throw new IOException(t.toString());
+            }
+        }
+    }
+
+    public static final ByteBuffer getReadableBuffer(ByteBuffer orig, BBucket bucket) {
+        if (orig == null || orig.array() != bucket.array()) {
+            orig = ByteBuffer.wrap(bucket.array());
+        }
+        orig.position(bucket.position());
+        orig.limit(bucket.limit());
+        return orig;
+    }
+
+    public static final void releaseReadableBuffer(ByteBuffer bb, BBucket bucket) {
+        bucket.position(bb.position());
+    }
+
+
+    public void readInterest(boolean b) throws IOException {
+        ch.readInterest(b);
+    }
+
+    public InetAddress getAddress(boolean remote) {
+        return ch.getAddress(remote);
+    }
+
+    @Override
+    public Object getAttribute(String name) {
+        if (ATT_REMOTE_HOSTNAME.equals(name)) {
+            return getAddress(true).getHostName();
+        } else if (ATT_LOCAL_HOSTNAME.equals(name)) {
+            return getAddress(false).getHostName();
+        } else if (ATT_REMOTE_ADDRESS.equals(name)) {
+            return getAddress(true).getHostAddress();
+        } else if (ATT_LOCAL_ADDRESS.equals(name)) {
+            return getAddress(false).getHostAddress();
+        } else if (ATT_REMOTE_PORT.equals(name)) {
+            return ch.getPort(true);
+        } else if (ATT_LOCAL_PORT.equals(name)) {
+            return ch.getPort(false);
+        }
+        return null;
+    }
+
+    public void startSending() throws IOException {
+        flush(ch);
+    }
+
+    public void shutdownOutput() throws IOException {
+        getOut().close();
+        if (ch != null) {
+            startSending();
+        }
+    }
+
+    @Override
+    public void handleClosed(NioChannel ch) throws IOException {
+        lastException = ch.lastException;
+        closed(); // our callback.
+    }
+
+    public void closed() throws IOException {
+        getIn().close();
+        sendHandleReceivedCallback();
+        //super.closed();
+    }
+
+    @Override
+    public void handleConnected(NioChannel ch) throws IOException {
+        setChannel(ch);
+        connectedCallback.handleConnected(this);
+    }
+
+    @Override
+    public void handleReadable(NioChannel ch) throws IOException {
+        handleReceived(this);
+    }
+
+    @Override
+    public void handleWriteable(NioChannel ch) throws IOException {
+        flush(ch);
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java
new file mode 100644
index 0000000..8c584b8
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/SslProvider.java
@@ -0,0 +1,24 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+
+public interface SslProvider {
+
+    public static final String ATT_SSL_CERT = "SslCert";
+    public static final String ATT_SSL_CIPHER = "SslCipher";
+    public static final String ATT_SSL_KEY_SIZE = "SslKeySize";
+    public static final String ATT_SSL_SESSION_ID = "SslSessionId";
+
+    /**
+     * Wrap channel with SSL.
+     *
+     * The result will start a handshake
+     */
+    public IOChannel channel(IOChannel net, String host, int port)
+        throws IOException;
+
+    public IOChannel serverChannel(IOChannel net) throws IOException;
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/UrlEncoding.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/UrlEncoding.java
new file mode 100644
index 0000000..09e4b53
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/UrlEncoding.java
@@ -0,0 +1,215 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.io;
+
+import java.io.CharConversionException;
+import java.io.IOException;
+import java.nio.charset.CharsetEncoder;
+import java.util.BitSet;
+
+
+/**
+ * Support for %xx URL encoding.
+ *
+ * @author Costin Manolache
+ */
+public final class UrlEncoding {
+
+    protected static final boolean ALLOW_ENCODED_SLASH =
+        Boolean.valueOf(
+                System.getProperty(
+                        "org.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH",
+                "false")).booleanValue();
+
+    public UrlEncoding() {
+    }
+
+    // Utilities for URL encoding.
+    static BitSet SAFE_CHARS_URL = new BitSet(128);
+    static BitSet SAFE_CHARS = new BitSet(128);
+    BBuffer tmpBuffer = BBuffer.allocate(1024);
+    CBuffer tmpCharBuffer = CBuffer.newInstance();
+
+    public void urlEncode(CBuffer url, CBuffer encoded, IOWriter enc) {
+        tmpBuffer.recycle();
+        urlEncode(url, tmpBuffer, encoded, enc.getEncoder("UTF-8"),
+                SAFE_CHARS_URL, true, enc);
+    }
+
+    public void urlEncode(String url, CBuffer encoded, IOWriter enc) {
+        tmpCharBuffer.recycle();
+        tmpCharBuffer.append(url);
+        urlEncode(tmpCharBuffer, encoded, enc);
+    }
+
+    /** Only works for UTF-8 or charsets preserving ascii.
+     *
+     * @param url
+     * @param tmpBuffer
+     * @param encoded
+     * @param utf8Enc
+     * @param safeChars
+     */
+    public void urlEncode(CBuffer url,
+            BBuffer tmpBuffer,
+            CBuffer encoded,
+            CharsetEncoder utf8Enc,
+            BitSet safeChars, boolean last, IOWriter enc) {
+        // tomcat charset-encoded each character first. I don't think
+        // this is needed.
+
+        // TODO: space to +
+        enc.encodeAll(url, tmpBuffer, utf8Enc, last);
+        byte[] array = tmpBuffer.array();
+        for (int i = tmpBuffer.position(); i < tmpBuffer.limit(); i++) {
+            int c = array[i];
+            if (safeChars.get(c)) {
+                encoded.append((char) c);
+            } else {
+                encoded.append('%');
+                char ch = Character.forDigit((c >> 4) & 0xF, 16);
+                encoded.append(ch);
+                ch = Character.forDigit(c & 0xF, 16);
+                encoded.append(ch);
+            }
+        }
+    }
+
+    static {
+        initSafeChars(SAFE_CHARS);
+        initSafeChars(SAFE_CHARS_URL);
+        SAFE_CHARS_URL.set('/');
+    }
+
+    private static void initSafeChars(BitSet safeChars) {
+        int i;
+        for (i = 'a'; i <= 'z'; i++) {
+            safeChars.set(i);
+        }
+        for (i = 'A'; i <= 'Z'; i++) {
+            safeChars.set(i);
+        }
+        for (i = '0'; i <= '9'; i++) {
+            safeChars.set(i);
+        }
+        // safe
+        safeChars.set('-');
+        safeChars.set('_');
+        safeChars.set('.');
+
+        // Dangerous: someone may treat this as " "
+        // RFC1738 does allow it, it's not reserved
+        // safeChars.set('+');
+        // extra
+        safeChars.set('*');
+        // tomcat has them - not sure if this is correct
+        safeChars.set('$'); // ?
+        safeChars.set('!'); // ?
+        safeChars.set('\''); // ?
+        safeChars.set('('); // ?
+        safeChars.set(')'); // ?
+        safeChars.set(','); // ?
+    }
+
+    public void urlDecode(BBuffer bb, CBuffer dest, boolean q,
+            IOReader charDec) throws IOException {
+        // Replace %xx
+        tmpBuffer.append(bb);
+        urlDecode(tmpBuffer, q);
+        charDec.decodeAll(bb, dest);
+    }
+
+
+    public void urlDecode(BBuffer bb, CBuffer dest,
+            IOReader charDec) throws IOException {
+        // Replace %xx
+        tmpBuffer.append(bb);
+        urlDecode(tmpBuffer, true);
+        charDec.decodeAll(bb, dest);
+    }
+
+
+    /**
+     * URLDecode, will modify the source. This is only at byte level -
+     * it needs conversion to chars using the right charset.
+     *
+     * @param query Converts '+' to ' ' and allow '/'
+     */
+    public void urlDecode(BBuffer mb, boolean query) throws IOException {
+        int start = mb.getOffset();
+        byte buff[] = mb.array();
+        int end = mb.getEnd();
+
+        int idx = BBuffer.indexOf(buff, start, end, '%');
+        int idx2 = -1;
+        if (query)
+            idx2 = BBuffer.indexOf(buff, start, end, '+');
+        if (idx < 0 && idx2 < 0) {
+            return;
+        }
+
+        // idx will be the smallest positive inxes ( first % or + )
+        if (idx2 >= 0 && idx2 < idx)
+            idx = idx2;
+        if (idx < 0)
+            idx = idx2;
+
+        //boolean noSlash = !query;
+
+        for (int j = idx; j < end; j++, idx++) {
+            if (buff[j] == '+' && query) {
+                buff[idx] = (byte) ' ';
+            } else if (buff[j] != '%') {
+                buff[idx] = buff[j];
+            } else {
+                // read next 2 digits
+                if (j + 2 >= end) {
+                    throw new CharConversionException("EOF");
+                }
+                byte b1 = buff[j + 1];
+                byte b2 = buff[j + 2];
+                if (!isHexDigit(b1) || !isHexDigit(b2))
+                    throw new CharConversionException("isHexDigit");
+
+                j += 2;
+                int res = x2c(b1, b2);
+//                if (noSlash && (res == '/')) {
+//                    throw new CharConversionException("noSlash " + mb);
+//                }
+                buff[idx] = (byte) res;
+            }
+        }
+
+        mb.setEnd(idx);
+
+        return;
+    }
+
+
+    private static boolean isHexDigit(int c) {
+        return ((c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F'));
+    }
+
+    private static int x2c(byte b1, byte b2) {
+        int digit = (b1 >= 'A') ? ((b1 & 0xDF) - 'A') + 10 : (b1 - '0');
+        digit *= 16;
+        digit += (b2 >= 'A') ? ((b2 & 0xDF) - 'A') + 10 : (b2 - '0');
+        return digit;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/WrappedException.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/WrappedException.java
new file mode 100644
index 0000000..5d0897c
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/WrappedException.java
@@ -0,0 +1,40 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+
+/**
+ * For specific exceptions - also has cause ( good if compiling against
+ * JDK1.5 )
+ *
+ * @author Costin Manolache
+ */
+public class WrappedException extends IOException {
+
+    public WrappedException() {
+        super();
+    }
+
+    public WrappedException(String message) {
+        super(message);
+    }
+
+    public WrappedException(String message, Throwable cause) {
+        super(message);
+        initCause(cause);
+    }
+
+    public WrappedException(Throwable cause) {
+        super("");
+        initCause(cause);
+    }
+
+
+    public static class ClientAbortException extends WrappedException {
+        public ClientAbortException(Throwable throwable) {
+            super(null, throwable);
+        }
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java
new file mode 100644
index 0000000..aad716a
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/JsseSslProvider.java
@@ -0,0 +1,466 @@
+/*
+ */
+package org.apache.tomcat.lite.io.jsse;
+
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.Socket;
+import java.security.Key;
+import java.security.KeyFactory;
+import java.security.KeyManagementException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.KeyStoreException;
+import java.security.NoSuchAlgorithmException;
+import java.security.Principal;
+import java.security.PrivateKey;
+import java.security.UnrecoverableKeyException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.spec.RSAKeyGenParameterSpec;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import javax.net.ssl.HostnameVerifier;
+import javax.net.ssl.KeyManager;
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.TrustManager;
+import javax.net.ssl.TrustManagerFactory;
+import javax.net.ssl.X509ExtendedKeyManager;
+import javax.net.ssl.X509TrustManager;
+
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.DumpChannel;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.SocketConnector;
+import org.apache.tomcat.lite.io.SslProvider;
+import org.apache.tomcat.lite.io.WrappedException;
+import org.apache.tomcat.lite.io.IOConnector.ConnectedCallback;
+
+
+public class JsseSslProvider implements SslProvider {
+
+    /**
+     * TODO: option to require validation.
+     * TODO: remember cert signature. This is needed to support self-signed
+     * certs, like those used by the test.
+     *
+     */
+    public static class BasicTrustManager implements X509TrustManager {
+
+        private X509Certificate[] chain;
+
+        public void checkClientTrusted(X509Certificate[] chain, String authType)
+                throws CertificateException {
+            this.chain = chain;
+        }
+
+        public void checkServerTrusted(X509Certificate[] chain, String authType)
+                throws CertificateException {
+            this.chain = chain;
+        }
+
+        public X509Certificate[] getAcceptedIssuers() {
+            return new X509Certificate[0];
+        }
+    }
+
+    public static TrustManager[] trustAllCerts = new TrustManager[] {
+        new BasicTrustManager() };
+
+    static String[] enabledCiphers;
+
+    static final boolean debug = false;
+
+    IOConnector net;
+    private KeyManager[] keyManager;
+    SSLContext sslCtx;
+    boolean server;
+    private TrustManager[] trustManagers;
+
+    public AtomicInteger handshakeCount = new AtomicInteger();
+    public AtomicInteger handshakeOk = new AtomicInteger();
+    public AtomicInteger handshakeErr = new AtomicInteger();
+    public AtomicInteger handshakeTime = new AtomicInteger();
+
+    Executor handshakeExecutor = Executors.newCachedThreadPool();
+    static int id = 0;
+
+    public JsseSslProvider() {
+    }
+
+    public static void setEnabledCiphers(String[] enabled) {
+        enabledCiphers = enabled;
+    }
+
+    public void start() {
+
+    }
+
+    SSLContext getSSLContext() {
+        if (sslCtx == null) {
+            try {
+                sslCtx = SSLContext.getInstance("TLS");
+                if (trustManagers == null) {
+                    trustManagers =
+                        new TrustManager[] {new BasicTrustManager()};
+
+                }
+                sslCtx.init(keyManager, trustManagers, null);
+            } catch (NoSuchAlgorithmException e) {
+                throw new RuntimeException(e);
+            } catch (KeyManagementException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+        return sslCtx;
+    }
+
+    public IOConnector getNet() {
+        if (net == null) {
+            getSSLContext();
+            net = new SocketConnector();
+        }
+        return net;
+    }
+
+    @Override
+    public IOChannel channel(IOChannel net, String host, int port) throws IOException {
+      if (debug) {
+          net = DumpChannel.wrap("S-ENC-" + id, net);
+        }
+        SslChannel ch = new SslChannel()
+            .setTarget(host, port)
+            .setSslContext(getSSLContext())
+            .setSslProvider(this);
+        net.setHead(ch);
+        return ch;
+    }
+
+    @Override
+    public SslChannel serverChannel(IOChannel net) throws IOException {
+        SslChannel ch = new SslChannel()
+            .setSslContext(getSSLContext())
+            .setSslProvider(this).withServer();
+        ch.setSink(net);
+        return ch;
+    }
+
+    public void acceptor(final ConnectedCallback sc, CharSequence port, Object extra)
+            throws IOException {
+        getNet().acceptor(new ConnectedCallback() {
+            @Override
+            public void handleConnected(IOChannel ch) throws IOException {
+                IOChannel first = ch;
+                if (debug) {
+                    first = DumpChannel.wrap("S-ENC-" + id, ch);
+                }
+
+                IOChannel sslch = serverChannel(first);
+                sslch.setSink(first);
+                first.setHead(sslch);
+
+                if (debug) {
+                    sslch = DumpChannel.wrap("S-CLR-" + id, sslch);
+                    id++;
+                }
+
+                sc.handleConnected(sslch);
+            }
+        }, port, extra);
+    }
+
+    public void connect(final String host, final int port, final ConnectedCallback sc)
+            throws IOException {
+        getNet().connect(host, port, new ConnectedCallback() {
+
+            @Override
+            public void handleConnected(IOChannel ch) throws IOException {
+                IOChannel first = ch;
+                if (debug) {
+                    first = DumpChannel.wrap("ENC-" + id, first);
+                }
+
+                IOChannel sslch = channel(first, host, port);
+//                first.setHead(sslch);
+
+                if (debug) {
+                    sslch = DumpChannel.wrap("CLR-" + id, sslch);
+                    id++;
+                }
+
+                sc.handleConnected(sslch);
+            }
+
+        });
+    }
+
+    public JsseSslProvider withKeyManager(KeyManager[] kms) {
+        this.keyManager = kms;
+        return this;
+    }
+
+    public JsseSslProvider setKeystoreFile(String file, String pass) throws IOException {
+        return setKeystore(new FileInputStream(file), pass);
+    }
+
+    public JsseSslProvider setKeystoreResource(String res, String pass) throws IOException {
+        return setKeystore(this.getClass().getClassLoader().getResourceAsStream(res),
+                pass);
+    }
+
+    public JsseSslProvider setKeystore(InputStream file, String pass) {
+        char[] passphrase = pass.toCharArray();
+        KeyStore ks;
+        try {
+            String type = KeyStore.getDefaultType();
+            System.err.println("Keystore: " + type);
+            // Java: JKS
+            // Android: BKS
+            ks = KeyStore.getInstance(type);
+            ks.load(file, passphrase);
+            KeyManagerFactory kmf =
+                KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+            kmf.init(ks, passphrase);
+
+            TrustManagerFactory tmf =
+                TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+            tmf.init(ks);
+
+            keyManager = kmf.getKeyManagers();
+            trustManagers = tmf.getTrustManagers();
+        } catch (KeyStoreException e) {
+            // No JKS keystore ?
+            // TODO Auto-generated catch block
+        }catch (NoSuchAlgorithmException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (CertificateException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (FileNotFoundException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (UnrecoverableKeyException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+
+        return this;
+    }
+
+    public JsseSslProvider setKeys(X509Certificate cert, PrivateKey privKey) {
+        keyManager = new KeyManager[] {
+                new TestKeyManager(cert, privKey)
+        };
+        return this;
+    }
+
+    public JsseSslProvider setKeyFiles(String certPem, String keyFile)
+            throws IOException {
+
+
+        return this;
+    }
+
+    public JsseSslProvider setKeyRes(String certPem, String keyFile)
+            throws IOException {
+        setKeys(this.getClass().getClassLoader().getResourceAsStream(certPem),
+                this.getClass().getClassLoader().getResourceAsStream(keyFile));
+        return this;
+    }
+
+    private void setKeys(InputStream certPem,
+            InputStream keyDer) throws IOException {
+        BBuffer keyB = BBuffer.allocate(2048);
+        keyB.readAll(keyDer);
+        byte[] key = new byte[keyB.remaining()];
+        keyB.getByteBuffer().get(key);
+
+        setKeys(certPem, key);
+    }
+
+    public JsseSslProvider setKeys(String certPem, byte[] keyBytes) throws IOException{
+        InputStream is = new ByteArrayInputStream(certPem.getBytes());
+        return setKeys(is, keyBytes);
+    }
+
+    /**
+     * Initialize using a PEM certificate and key bytes.
+     * ( TODO: base64 dep to set the key as PEM )
+     *
+     *  openssl genrsa 1024 > host.key
+     *  openssl pkcs8 -topk8 -nocrypt -in host.key -inform PEM
+     *     -out host.der -outform DER
+     *  openssl req -new -x509 -nodes -sha1 -days 365 -key host.key > host.cert
+     *
+     */
+    public JsseSslProvider setKeys(InputStream certPem, byte[] keyBytes) throws IOException{
+        // convert key
+        try {
+            KeyFactory kf = KeyFactory.getInstance("RSA");
+            PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(keyBytes);
+            PrivateKey priv = kf.generatePrivate (keysp);
+
+            // Convert cert pem to certificate
+            CertificateFactory cf = CertificateFactory.getInstance("X.509");
+            final X509Certificate cert =  (X509Certificate) cf.generateCertificate(certPem);
+
+            setKeys(cert, priv);
+        } catch (Throwable t) {
+            throw new WrappedException(t);
+        }
+        return this;
+    }
+
+    public class TestKeyManager extends X509ExtendedKeyManager {
+        X509Certificate cert;
+        PrivateKey privKey;
+
+        public TestKeyManager(X509Certificate cert2, PrivateKey privKey2) {
+            cert = cert2;
+            privKey = privKey2;
+        }
+
+        public String chooseEngineClientAlias(String[] keyType,
+                java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine) {
+            return "client";
+        }
+
+        public String chooseEngineServerAlias(String keyType,
+                java.security.Principal[] issuers, javax.net.ssl.SSLEngine engine) {
+            return "server";
+        }
+
+        public String chooseClientAlias(String[] keyType,
+                                        Principal[] issuers, Socket socket) {
+            return "client";
+        }
+
+        public String chooseServerAlias(String keyType,
+                                        Principal[] issuers, Socket socket) {
+            return "server";
+        }
+
+        public X509Certificate[] getCertificateChain(String alias) {
+            return new X509Certificate[] {cert};
+        }
+
+        public String[] getClientAliases(String keyType, Principal[] issuers) {
+            return null;
+        }
+
+        public PrivateKey getPrivateKey(String alias) {
+
+            return privKey;
+        }
+
+        public String[] getServerAliases(String keyType, Principal[] issuers) {
+            return null;
+        }
+    }
+
+    // TODO: add a mode that trust a defined list of certs, like SSH
+
+    /**
+     * Make URLConnection accept all certificates.
+     * Use only for testing !
+     */
+    public static void testModeURLConnection() {
+        try {
+            SSLContext sc = SSLContext.getInstance("TLS");
+            sc.init(null, JsseSslProvider.trustAllCerts, null);
+
+            javax.net.ssl.HttpsURLConnection.setDefaultSSLSocketFactory(
+                    sc.getSocketFactory());
+            javax.net.ssl.HttpsURLConnection.setDefaultHostnameVerifier(
+                    new HostnameVerifier() {
+
+                        @Override
+                        public boolean verify(String hostname,
+                                SSLSession session) {
+                            try {
+                                Certificate[] certs = session.getPeerCertificates();
+                                // TODO...
+                                // see org/apache/http/conn/ssl/AbstractVerifier
+                            } catch (SSLPeerUnverifiedException e) {
+                                e.printStackTrace();
+                            }
+                            return true;
+                        }
+
+                    });
+
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+    }
+
+    // Utilities
+    public static byte[] getPrivateKeyFromStore(String file, String pass)
+            throws Exception {
+        KeyStore store = KeyStore.getInstance("JKS");
+        store.load(new FileInputStream(file), pass.toCharArray());
+        Key key = store.getKey("tomcat", "changeit".toCharArray());
+        PrivateKey pk = (PrivateKey) key;
+        byte[] encoded = pk.getEncoded();
+        return encoded;
+    }
+
+    public static byte[] getCertificateFromStore(String file, String pass)
+            throws Exception {
+        KeyStore store = KeyStore.getInstance("JKS");
+        store.load(new FileInputStream(file), pass.toCharArray());
+        Certificate certificate = store.getCertificate("tomcat");
+
+        return certificate.getEncoded();
+    }
+
+    public static KeyPair generateRsaOrDsa(boolean rsa) throws Exception {
+        if (rsa) {
+            KeyPairGenerator keyPairGen =
+                KeyPairGenerator.getInstance("RSA");
+            keyPairGen.initialize(1024);
+
+            RSAKeyGenParameterSpec keySpec = new RSAKeyGenParameterSpec(1024,
+                    RSAKeyGenParameterSpec.F0);
+            keyPairGen.initialize(keySpec);
+
+            KeyPair rsaKeyPair = keyPairGen.generateKeyPair();
+
+            return rsaKeyPair;
+        } else {
+            KeyPairGenerator keyPairGen =
+                KeyPairGenerator.getInstance("DSA");
+            keyPairGen.initialize(1024);
+
+            KeyPair pair = keyPairGen.generateKeyPair();
+
+            return pair;
+        }
+    }
+
+    /**
+     * I know 2 ways to generate certs:
+     *  - keytool
+     *  - openssl req -x509 -nodes -days 365 \
+     *    -newkey rsa:1024 -keyout mycert.pem -out mycert.pem
+     *  openssl s_server -accept 9443 -cert mycert.pem -debug -msg -state -www
+     */
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java
new file mode 100644
index 0000000..87705df
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/jsse/SslChannel.java
@@ -0,0 +1,636 @@
+/*
+ */
+package org.apache.tomcat.lite.io.jsse;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.security.GeneralSecurityException;
+import java.util.concurrent.TimeoutException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.SSLEngineResult;
+import javax.net.ssl.SSLException;
+import javax.net.ssl.SSLPeerUnverifiedException;
+import javax.net.ssl.SSLSession;
+import javax.net.ssl.SSLEngineResult.HandshakeStatus;
+import javax.net.ssl.SSLEngineResult.Status;
+
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.SslProvider;
+
+class SslChannel extends IOChannel implements Runnable {
+
+    static Logger log = Logger.getLogger("SSL");
+
+    static ByteBuffer EMPTY = ByteBuffer.allocate(0);
+
+
+    SSLEngine sslEngine;
+    // Last result
+    SSLEngineResult unwrapR;
+
+    boolean handshakeDone = false;
+    boolean handshakeInProgress = false;
+    Object handshakeSync = new Object();
+    boolean flushing = false;
+
+    IOBuffer in = new IOBuffer(this);
+    IOBuffer out = new IOBuffer(this);
+
+    long handshakeTimeout = 20000;
+    // Used for session reuse
+    String host;
+    int port;
+
+    ByteBuffer myAppOutData;
+    ByteBuffer myNetOutData;
+    private static boolean debugWrap = false;
+
+    /*
+     * Special: SSL works in packet mode, and we may receive an incomplete
+     * packet. This should be in compacted write mode (i.e. data from 0 to pos,
+     * limit at end )
+     */
+    ByteBuffer myNetInData;
+    ByteBuffer myAppInData;
+    boolean client = true;
+
+    private SSLContext sslCtx;
+
+    private boolean closeHandshake = false;
+
+    public SslChannel() {
+    }
+
+    /**
+     * Setting the host/port enables clients to reuse SSL session -
+     * less traffic and encryption overhead at startup, assuming the
+     * server caches the session ( i.e. single server or distributed cache ).
+     *
+     * SSL ticket extension is another possibility.
+     */
+    public SslChannel setTarget(String host, int port) {
+        this.host = host;
+        this.port = port;
+        return this;
+    }
+
+    private synchronized void initSsl() throws GeneralSecurityException {
+        if (sslEngine != null) {
+            log.severe("Double initSsl");
+            return;
+        }
+
+        if (client) {
+            if (port > 0) {
+                sslEngine = sslCtx.createSSLEngine(host, port);
+            } else {
+                sslEngine = sslCtx.createSSLEngine();
+            }
+            sslEngine.setUseClientMode(client);
+        } else {
+            sslEngine = sslCtx.createSSLEngine();
+            sslEngine.setUseClientMode(false);
+
+        }
+
+        // Some VMs have broken ciphers.
+        if (JsseSslProvider.enabledCiphers != null) {
+            sslEngine.setEnabledCipherSuites(JsseSslProvider.enabledCiphers);
+        }
+
+        SSLSession session = sslEngine.getSession();
+
+        int packetBuffer = session.getPacketBufferSize();
+        myAppOutData = ByteBuffer.allocate(session.getApplicationBufferSize());
+        myNetOutData = ByteBuffer.allocate(packetBuffer);
+        myAppInData = ByteBuffer.allocate(session.getApplicationBufferSize());
+        myNetInData = ByteBuffer.allocate(packetBuffer);
+        myNetInData.flip();
+        myNetOutData.flip();
+        myAppInData.flip();
+        myAppOutData.flip();
+    }
+
+    public SslChannel withServer() {
+        client = false;
+        return this;
+    }
+
+
+    @Override
+    public synchronized void setSink(IOChannel net) throws IOException {
+        try {
+            if (sslEngine == null) {
+                initSsl();
+            }
+            super.setSink(net);
+        } catch (GeneralSecurityException e) {
+            log.log(Level.SEVERE, "Error initializing ", e);
+        }
+    }
+
+    @Override
+    public IOBuffer getIn() {
+        return in;
+    }
+
+    @Override
+    public IOBuffer getOut() {
+        return out;
+    }
+
+    /**
+     * Typically called when a dataReceived callback is passed up.
+     * It's up to the higher layer to decide if it can handle more data
+     * and disable read interest and manage its buffers.
+     *
+     * We have to use one buffer.
+     * @throws IOException
+     */
+    public int processInput(IOBuffer netIn, IOBuffer appIn) throws IOException {
+        if (log.isLoggable(Level.FINEST)) {
+            log.info("JSSE: processInput " + handshakeInProgress + " " + netIn.getBufferCount());
+        }
+        synchronized(handshakeSync) {
+            if (!handshakeDone && !handshakeInProgress) {
+                handshakeInProgress = true;
+                handleHandshking();
+                return 0;
+            }
+            if (handshakeInProgress) {
+                return 0; // leave it there
+            }
+        }
+        return processRealInput(netIn, appIn);
+    }
+
+    private synchronized int processRealInput(IOBuffer netIn, IOBuffer appIn) throws IOException {
+        int rd = 0;
+        boolean needsMore = true;
+        boolean notEnough = false;
+
+        while (needsMore) {
+            if (netIn.isClosedAndEmpty()) {
+                appIn.close();
+                sendHandleReceivedCallback();
+                return -1;
+            }
+            myNetInData.compact();
+            int rdNow;
+            try {
+                rdNow = netIn.read(myNetInData);
+            } finally {
+                myNetInData.flip();
+            }
+            if (rdNow == 0 && (myNetInData.remaining() == 0 ||
+                    notEnough)) {
+                return rd;
+            }
+            if (rdNow == -1) {
+                appIn.close();
+                sendHandleReceivedCallback();
+                return rd;
+            }
+
+            notEnough = true; // next read of 0
+            while (myNetInData.remaining() > 0) {
+                myAppInData.compact();
+                try {
+                    unwrapR = sslEngine.unwrap(myNetInData, myAppInData);
+                } catch (SSLException ex) {
+                    log.warning("Read error: " + ex);
+                    close();
+                    return -1;
+                } finally {
+                    myAppInData.flip();
+                }
+                if (myAppInData.remaining() > 0) {
+                    in.write(myAppInData); // all will be written
+                }
+                if (unwrapR.getStatus() == Status.CLOSED) {
+                    in.close();
+                    if (unwrapR.getHandshakeStatus() == HandshakeStatus.NEED_WRAP) {
+                        // TODO: send/receive one more packet ( handshake mode ? )
+                        synchronized(handshakeSync) {
+                            handshakeInProgress = true;
+                            closeHandshake  = true;
+                        }
+                        handleHandshking();
+
+                        startSending();
+                    }
+                    break;
+                }
+
+                if (unwrapR.getHandshakeStatus() == HandshakeStatus.NEED_TASK) {
+                    tasks();
+                }
+                if (unwrapR.getStatus() == Status.BUFFER_OVERFLOW) {
+                    log.severe("Unhandled overflow " + unwrapR);
+                    break;
+                }
+                if (unwrapR.getStatus() == Status.BUFFER_UNDERFLOW) {
+                    // harmless
+                    //log.severe("Unhandled underflow " + unwrapR);
+                    break;
+                }
+            }
+            sendHandleReceivedCallback();
+
+
+        }
+        return rd;
+    }
+
+    protected SSLEngineResult.HandshakeStatus tasks() {
+        Runnable r = null;
+        while ( (r = sslEngine.getDelegatedTask()) != null) {
+            r.run();
+        }
+        return sslEngine.getHandshakeStatus();
+    }
+
+    public void startSending() throws IOException {
+
+        flushing = true;
+        boolean needHandshake = false;
+        synchronized(handshakeSync) {
+            if (handshakeInProgress) {
+                return; // don't bother me.
+            }
+            if (!handshakeDone) {
+                handshakeInProgress = true;
+                needHandshake = true;
+            }
+        }
+        if (needHandshake) {
+            handleHandshking();
+            return; // can't write yet.
+        }
+
+        startRealSending();
+    }
+
+    public void close() throws IOException {
+        if (net.getOut().isAppendClosed()) {
+            return;
+        }
+        sslEngine.closeOutbound(); // mark as closed
+        synchronized(myNetOutData) {
+            myNetOutData.compact();
+
+            SSLEngineResult wrap;
+            try {
+                wrap = sslEngine.wrap(EMPTY, myNetOutData);
+                if (wrap.getStatus() != Status.CLOSED) {
+                    log.warning("Unexpected close status " + wrap);
+                }
+            } catch (Throwable t ) {
+                log.info("Error wrapping " + myNetOutData);
+            } finally {
+                myNetOutData.flip();
+            }
+            if (myNetOutData.remaining() > 0) {
+                net.getOut().write(myNetOutData);
+            }
+        }
+        // TODO: timer to close socket if we don't get
+        // clean close handshake
+        super.close();
+    }
+
+    private Object sendLock = new Object();
+
+    private JsseSslProvider sslProvider;
+
+    private void startRealSending() throws IOException {
+        // Only one thread at a time
+        synchronized (sendLock) {
+            while (true) {
+
+                myAppOutData.compact();
+                int rd;
+                try {
+                    rd = out.read(myAppOutData);
+                } finally {
+                    myAppOutData.flip();
+                }
+                if (rd == 0) {
+                    break;
+                }
+                if (rd < 0) {
+                    close();
+                    break;
+                }
+
+                SSLEngineResult wrap;
+                synchronized(myNetOutData) {
+                    myNetOutData.compact();
+                    try {
+                        wrap = sslEngine.wrap(myAppOutData,
+                                myNetOutData);
+                    } finally {
+                        myNetOutData.flip();
+                    }
+                    net.getOut().write(myNetOutData);
+                }
+                if (wrap != null) {
+                    switch (wrap.getStatus()) {
+                    case BUFFER_UNDERFLOW: {
+                        break;
+                    }
+                    case OK: {
+                        break;
+                    }
+                    case BUFFER_OVERFLOW: {
+                        throw new IOException("Overflow");
+                    }
+                    }
+                }
+            }
+        }
+
+        net.startSending();
+    }
+
+
+    // SSL handshake require slow tasks - that will need to be executed in a
+    // thread anyways. Better to keep it simple ( the code is very complex ) -
+    // and do the initial handshake in a thread, not in the IO thread.
+    // We'll need to unregister and register again from the selector.
+    private void handleHandshking() {
+        if (log.isLoggable(Level.FINEST)) {
+            log.info("Starting handshake");
+        }
+        synchronized(handshakeSync) {
+            handshakeInProgress = true;
+        }
+
+        sslProvider.handshakeExecutor.execute(this);
+    }
+
+    private void endHandshake() throws IOException {
+        if (log.isLoggable(Level.FINEST)) {
+            log.info("Handshake done " + net.getIn().available());
+        }
+        synchronized(handshakeSync) {
+            handshakeDone = true;
+            handshakeInProgress = false;
+        }
+        if (flushing) {
+            flushing = false;
+            startSending();
+        }
+        if (myNetInData.remaining() > 0 || net.getIn().available() > 0) {
+            // Last SSL packet also includes data.
+            handleReceived(net);
+        }
+    }
+
+    /**
+     * Actual handshake magic, in background thread.
+     */
+    public void run() {
+        try {
+            boolean initial = true;
+            SSLEngineResult wrap = null;
+
+            HandshakeStatus hstatus = sslEngine.getHandshakeStatus();
+            if (!closeHandshake &&
+                    (hstatus == HandshakeStatus.NOT_HANDSHAKING || initial)) {
+                sslEngine.beginHandshake();
+                hstatus = sslEngine.getHandshakeStatus();
+            }
+
+            long t0 = System.currentTimeMillis();
+
+            while (hstatus != HandshakeStatus.NOT_HANDSHAKING
+                    && hstatus != HandshakeStatus.FINISHED
+                    && !net.getIn().isAppendClosed()) {
+                if (System.currentTimeMillis() - t0 > handshakeTimeout) {
+                    throw new TimeoutException();
+                }
+                if (wrap != null && wrap.getStatus() == Status.CLOSED) {
+                    break;
+                }
+                if (log.isLoggable(Level.FINEST)) {
+                    log.info("-->doHandshake() loop: status = " + hstatus + " " +
+                            sslEngine.getHandshakeStatus());
+                }
+
+                if (hstatus == HandshakeStatus.NEED_WRAP) {
+                    // || initial - for client
+                    initial = false;
+                    synchronized(myNetOutData) {
+                        while (hstatus == HandshakeStatus.NEED_WRAP) {
+                            myNetOutData.compact();
+                            try {
+                                wrap = sslEngine.wrap(myAppOutData, myNetOutData);
+                            } catch (Throwable t) {
+                                log.log(Level.SEVERE, "Wrap error", t);
+                                close();
+                                return;
+                            } finally {
+                                myNetOutData.flip();
+                            }
+                            if (myNetOutData.remaining() > 0) {
+                                net.getOut().write(myNetOutData);
+                            }
+                            hstatus = wrap.getHandshakeStatus();
+                        }
+                    }
+                    net.startSending();
+                } else if (hstatus == HandshakeStatus.NEED_UNWRAP) {
+                    while (hstatus == HandshakeStatus.NEED_UNWRAP) {
+                        // If we have few remaining bytes - process them
+                        if (myNetInData.remaining() > 0) {
+                            myAppInData.clear();
+                            if (debugWrap) {
+                                log.info("UNWRAP: rem=" + myNetInData.remaining());
+                            }
+                            wrap = sslEngine.unwrap(myNetInData, myAppInData);
+                            hstatus = wrap.getHandshakeStatus();
+                            myAppInData.flip();
+                            if (myAppInData.remaining() > 0) {
+                                log.severe("Unexpected data after unwrap");
+                            }
+                            if (wrap.getStatus() == Status.CLOSED) {
+                                break;
+                            }
+                        }
+                        // Still need unwrap
+                        if (wrap == null
+                                || wrap.getStatus() == Status.BUFFER_UNDERFLOW
+                                || (hstatus == HandshakeStatus.NEED_UNWRAP && myNetInData.remaining() == 0)) {
+                            myNetInData.compact();
+                            // non-blocking
+                            int rd;
+                            try {
+                                rd = net.getIn().read(myNetInData);
+                                if (debugWrap) {
+                                    log.info("Read: " + rd);
+                                }
+                            } finally {
+                                myNetInData.flip();
+                            }
+                            if (rd == 0) {
+                                if (debugWrap) {
+                                    log.info("Wait: " + handshakeTimeout);
+                                }
+                                net.getIn().waitData(handshakeTimeout);
+                                rd = net.getIn().read(myNetInData);
+                                if (debugWrap) {
+                                    log.info("Read after wait: " + rd);
+                                }
+                            }
+                            if (rd < 0) {
+                                // in closed
+                                break;
+                            }
+                        }
+                        if (log.isLoggable(Level.FINEST)) {
+                            log.info("Unwrap chunk done " + hstatus + " " + wrap
+                                + " " + sslEngine.getHandshakeStatus());
+                        }
+
+                    }
+
+                    // rd may have some input bytes.
+                } else if (hstatus == HandshakeStatus.NEED_TASK) {
+                    long t0task = System.currentTimeMillis();
+                    Runnable r;
+                    while ((r = sslEngine.getDelegatedTask()) != null) {
+                        r.run();
+                    }
+                    long t1task = System.currentTimeMillis();
+                    hstatus = sslEngine.getHandshakeStatus();
+                    if (log.isLoggable(Level.FINEST)) {
+                        log.info("Tasks done in " + (t1task - t0task) + " new status " +
+                                hstatus);
+                    }
+
+                }
+                if (hstatus == HandshakeStatus.NOT_HANDSHAKING) {
+                    //log.warning("NOT HANDSHAKING " + this);
+                    break;
+                }
+            }
+            endHandshake();
+            processRealInput(net.getIn(), in);
+        } catch (Throwable t) {
+            log.log(Level.SEVERE, "Error handshaking", t);
+            try {
+                close();
+                net.close();
+                sendHandleReceivedCallback();
+            } catch (IOException ex) {
+                log.log(Level.SEVERE, "Error closing", ex);
+            }
+        }
+    }
+
+
+    @Override
+    public void handleReceived(IOChannel ch) throws IOException {
+        processInput(net.getIn(), in);
+        // Maybe we don't have data - that's fine.
+        sendHandleReceivedCallback();
+    }
+
+    SslChannel setSslContext(SSLContext sslCtx) {
+        this.sslCtx = sslCtx;
+        return this;
+    }
+
+    SslChannel setSslProvider(JsseSslProvider con) {
+        this.sslProvider = con;
+        return this;
+    }
+
+    public Object getAttribute(String name) {
+        if (SslProvider.ATT_SSL_CERT.equals(name)) {
+            try {
+                return sslEngine.getSession().getPeerCertificateChain();
+            } catch (SSLPeerUnverifiedException e) {
+                return null; // no re-negotiation
+            }
+        } else if (SslProvider.ATT_SSL_CIPHER.equals(name)) {
+            return sslEngine.getSession().getCipherSuite();
+        } else if (SslProvider.ATT_SSL_KEY_SIZE.equals(name)) {
+            // looks like we need to get it from the string cipher
+            CipherData c_aux[] = ciphers;
+
+            int size = 0;
+            String cipherSuite = sslEngine.getSession().getCipherSuite();
+            for (int i = 0; i < c_aux.length; i++) {
+                if (cipherSuite.indexOf(c_aux[i].phrase) >= 0) {
+                    size = c_aux[i].keySize;
+                    break;
+                }
+            }
+            return size;
+        } else if (SslProvider.ATT_SSL_SESSION_ID.equals(name)) {
+            byte [] ssl_session = sslEngine.getSession().getId();
+            if ( ssl_session == null)
+                return null;
+            StringBuilder buf=new StringBuilder();
+            for(int x=0; x<ssl_session.length; x++) {
+                String digit=Integer.toHexString(ssl_session[x]);
+                if (digit.length()<2) buf.append('0');
+                if (digit.length()>2) digit=digit.substring(digit.length()-2);
+                buf.append(digit);
+            }
+            return buf.toString();
+        }
+
+        if (net != null) {
+            return net.getAttribute(name);
+        }
+        return null;
+    }
+
+
+     /**
+      * Simple data class that represents the cipher being used, along with the
+      * corresponding effective key size.  The specified phrase must appear in the
+      * name of the cipher suite to be recognized.
+      */
+
+     static final class CipherData {
+
+         public String phrase = null;
+
+         public int keySize = 0;
+
+         public CipherData(String phrase, int keySize) {
+             this.phrase = phrase;
+             this.keySize = keySize;
+         }
+
+     }
+
+
+     /**
+      * A mapping table to determine the number of effective bits in the key
+      * when using a cipher suite containing the specified cipher name.  The
+      * underlying data came from the TLS Specification (RFC 2246), Appendix C.
+      */
+      static final CipherData ciphers[] = {
+         new CipherData("_WITH_NULL_", 0),
+         new CipherData("_WITH_IDEA_CBC_", 128),
+         new CipherData("_WITH_RC2_CBC_40_", 40),
+         new CipherData("_WITH_RC4_40_", 40),
+         new CipherData("_WITH_RC4_128_", 128),
+         new CipherData("_WITH_DES40_CBC_", 40),
+         new CipherData("_WITH_DES_CBC_", 56),
+         new CipherData("_WITH_3DES_EDE_CBC_", 168),
+         new CipherData("_WITH_AES_128_CBC_", 128),
+         new CipherData("_WITH_AES_256_CBC_", 256)
+     };
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/io/package.html b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/package.html
new file mode 100644
index 0000000..07a0f30
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/io/package.html
@@ -0,0 +1,7 @@
+IO layer based on tomcat coyote connector and utils.
+
+There are many big changes:
+<ul>
+  <li>
+  <li>
+</ul>
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/CopyCallback.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/CopyCallback.java
new file mode 100644
index 0000000..cfb499d
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/CopyCallback.java
@@ -0,0 +1,57 @@
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+/**
+ *  Used by socks and http proxy. Will copy received data to a different
+ *  channel.
+ */
+public class CopyCallback implements IOConnector.DataReceivedCallback {
+        IOChannel mOutBuffer;
+
+        public CopyCallback(IOChannel sc) {
+            mOutBuffer = sc;
+        }
+
+        @Override
+        public void handleReceived(IOChannel ch) throws IOException {
+            IOBuffer inBuffer = ch.getIn();
+            IOChannel outBuffer = mOutBuffer;
+            if (outBuffer == null &&
+                    ch instanceof HttpChannel) {
+                outBuffer =
+                    (IOChannel) ((HttpChannel)ch).getRequest().getAttribute("P");
+            }
+            // body.
+            while (true) {
+                if (outBuffer == null || outBuffer.getOut() == null) {
+                    return;
+                }
+                if (outBuffer.getOut().isAppendClosed()) {
+                    return;
+                }
+
+                ByteBuffer bb = outBuffer.getOut().getWriteBuffer();
+                int rd = inBuffer.read(bb);
+                outBuffer.getOut().releaseWriteBuffer(rd);
+
+                if (rd == 0) {
+                    outBuffer.startSending();
+                    return;
+                }
+                if (rd < 0) {
+                    outBuffer.getOut().close();
+                    outBuffer.startSending();
+                    return;
+                }
+            }
+        }
+    }
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/HttpProxyService.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/HttpProxyService.java
new file mode 100644
index 0000000..6c426b7
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/HttpProxyService.java
@@ -0,0 +1,368 @@
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.MultiMap;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.SocketConnector;
+
+/**
+ * Http callback for the server-side. Will forward all requests to
+ * a remote http server - either using proxy mode ( GET http://... ) or
+ * forward requests ( GET /foo -> will be served by the remote server ).
+ *
+ * This is not blocking (except the connect, which currenly blocks on dns).
+ */
+public class HttpProxyService implements HttpService {
+
+    // target - when used in forwarding mode.
+    String target = "localhost";
+    int port = 8802;
+
+    static Logger log = Logger.getLogger("HttpProxy");
+    public static boolean debug = false;
+    boolean keepOpen = true;
+
+    // client side - this connect to the real server that generates the resp.
+    ProxyClientCallback clientHeadersReceived = new ProxyClientCallback();
+
+    HttpConnector httpConnector;
+    IOConnector ioConnector;
+
+    public HttpProxyService withSelector(IOConnector pool) {
+        this.ioConnector = pool;
+        return this;
+    }
+
+    public HttpProxyService withHttpClient(HttpConnector pool) {
+        this.httpConnector = pool;
+        return this;
+    }
+
+    public HttpProxyService withTarget(String host, int port) {
+        this.target = host;
+        this.port = port;
+        return this;
+    }
+
+    private IOConnector getSelector() {
+        if (ioConnector == null) {
+            ioConnector = new SocketConnector();
+        }
+        return ioConnector;
+    }
+
+    private HttpConnector getHttpConnector() {
+        if (httpConnector == null) {
+            httpConnector = new HttpConnector(getSelector());
+        }
+        return httpConnector;
+    }
+
+    // Connects to the target CONNECT server, as client, forwards
+    static class ProxyConnectClientConnection implements IOConnector.ConnectedCallback {
+
+        IOChannel serverNet;
+        private HttpChannel serverHttp;
+
+        public ProxyConnectClientConnection(HttpChannel sproc) throws IOException {
+            this.serverNet = sproc.getSink();
+            this.serverHttp = sproc;
+        }
+
+        @Override
+        public void handleConnected(IOChannel ioch) throws IOException {
+            if (!ioch.isOpen()) {
+                serverNet.close();
+                log.severe("Connection failed");
+                return;
+            }
+            afterClientConnect(ioch);
+
+            ioch.setDataReceivedCallback(new CopyCallback(serverNet));
+            //ioch.setDataFlushedCallback(new ProxyFlushedCallback(serverNet, ioch));
+            serverNet.setDataReceivedCallback(new CopyCallback(ioch));
+            //serverNet.setDataFlushedCallback(new ProxyFlushedCallback(ioch, serverNet));
+
+            ioch.sendHandleReceivedCallback();
+        }
+
+        static byte[] OK = "HTTP/1.1 200 OK\r\n\r\n".getBytes();
+
+        protected void afterClientConnect(IOChannel clientCh) throws IOException {
+            serverNet.getOut().queue(OK);
+            serverNet.startSending();
+
+            serverHttp.release(); // no longer used
+        }
+    }
+
+    /**
+     * Parse the req, dispatch the connection.
+     */
+    @Override
+    public void service(HttpRequest serverHttpReq, HttpResponse serverHttpRes)
+            throws IOException {
+
+        String dstHost = target; // default target ( for normal req ).
+        int dstPort = port;
+
+        // TODO: more flexibility/callbacks on selecting the target, acl, etc
+        if (serverHttpReq.method().equals("CONNECT")) {
+            // SSL proxy - just connect and forward all packets
+            // TODO: optimize, add error checking
+            String[] hostPort = serverHttpReq.requestURI().toString().split(":");
+            String host = hostPort[0];
+            int port = 443;
+            if (hostPort.length > 1) {
+                port = Integer.parseInt(hostPort[1]);
+            }
+            if (log.isLoggable(Level.FINE)) {
+                HttpChannel server = serverHttpReq.getHttpChannel();
+                log.info("NEW: " + server.getId() + " " + dstHost + " "  +
+                        server.getRequest().getMethod() +
+                        " " + server.getRequest().getRequestURI() + " " +
+                        server.getIn());
+            }
+
+            try {
+                getSelector().connect(host, port,
+                        new ProxyConnectClientConnection(serverHttpReq.getHttpChannel()));
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+            return;
+        }
+
+
+        CBuffer origURIx = serverHttpReq.requestURI();
+//        String origURI = origURIx.toString();
+//        if (origURI.startsWith("http://")) {
+//            // Real proxy - extract client address, modify the uri.
+//            // TODO: optimize the strings.
+//            int start = origURI.indexOf('/', 7);
+//            String hostPortS = (start == -1) ?
+//                    origURI.subSequence(7, origURI.length()).toString() :
+//                    origURI.subSequence(7, start).toString();
+//            String[] hostPort = hostPortS.split(":");
+//
+//            dstHost = hostPort[0];
+//            dstPort = (hostPort.length > 1) ? Integer.parseInt(hostPort[1]) :
+//                80;
+//
+//            if (start >= 0) {
+//                serverHttpReq.requestURI().set(origURI.substring(start));
+//            } else {
+//                serverHttpReq.requestURI().set("/");
+//            }
+//        } else {
+            // Adjust the host header.
+            CBuffer hostHdr =
+                serverHttpReq.getMimeHeaders().getHeader("host");
+            if (hostHdr != null) {
+                hostHdr.recycle();
+                CBuffer cb = hostHdr;
+                cb.append(dstHost);
+                if (dstPort != 80) {
+                    cb.append(':');
+                    cb.appendInt(dstPort);
+                }
+            }
+//        }
+        if (debug) {
+            HttpChannel server = serverHttpReq.getHttpChannel();
+            log.info("START: " + server.getId() + " " + dstHost + " "  +
+                    server.getRequest().getMethod() +
+                    " " + server.getRequest().getRequestURI() + " " +
+                    server.getIn());
+        }
+
+        // Send the request with a non-blocking write
+        HttpChannel serverHttp = serverHttpReq.getHttpChannel();
+
+        // Client connection
+        HttpChannel httpClient = getHttpConnector().get(dstHost, dstPort);
+
+        serverHttp.getRequest().setAttribute("CLIENT", httpClient);
+        httpClient.getRequest().setAttribute("SERVER", serverHttp);
+        serverHttp.getRequest().setAttribute("P", httpClient);
+        httpClient.getRequest().setAttribute("P", serverHttp);
+
+        httpClient.setHttpService(clientHeadersReceived);
+
+        // Will send the original request (TODO: small changes)
+        // Response is not affected ( we use the callback )
+        httpClient.getRequest().method().set(serverHttp.getRequest().method());
+        httpClient.getRequest().requestURI().set(serverHttp.getRequest().requestURI());
+        if (serverHttp.getRequest().queryString().length() != 0) {
+            httpClient.getRequest().queryString().set(serverHttp.getRequest().queryString());
+        }
+
+        httpClient.getRequest().protocol().set(serverHttp.getRequest().protocol());
+
+        //cstate.reqHeaders.addValue(name)
+        copyHeaders(serverHttp.getRequest().getMimeHeaders(),
+                httpClient.getRequest().getMimeHeaders() /*dest*/);
+
+        // For debug
+        httpClient.getRequest().getMimeHeaders().remove("Accept-Encoding");
+
+        if (!keepOpen) {
+            httpClient.getRequest().getMimeHeaders().setValue("Connection").set("Close");
+        }
+
+        // Any data
+        serverHttp.setDataReceivedCallback(copy);
+        copy.handleReceived(serverHttp);
+
+        httpClient.send();
+
+
+        //serverHttp.handleReceived(serverHttp.getSink());
+        //httpClient.flush(); // send any data still there
+
+        httpClient.setCompletedCallback(done);
+        // Will call release()
+        serverHttp.setCompletedCallback(done);
+
+        serverHttpReq.async();
+    }
+
+    static HttpDoneCallback done = new HttpDoneCallback();
+    static CopyCallback copy = new CopyCallback(null);
+    // POST: after sendRequest(ch) we need to forward the body !!
+
+
+    static void copyHeaders(MultiMap mimeHeaders, MultiMap dest)
+            throws IOException {
+        for (int i = 0; i < mimeHeaders.size(); i++) {
+            CBuffer name = mimeHeaders.getName(i);
+            CBuffer val = dest.addValue(name.toString());
+            val.set(mimeHeaders.getValue(i));
+        }
+    }
+
+    /**
+     * HTTP _CLIENT_ callback - from tomcat to final target.
+     */
+    public class ProxyClientCallback implements HttpService {
+        /**
+         * Headers received from the client (content http server).
+         * TODO: deal with version missmatches.
+         */
+        @Override
+        public void service(HttpRequest clientHttpReq, HttpResponse clientHttpRes) throws IOException {
+            HttpChannel serverHttp = (HttpChannel) clientHttpReq.getAttribute("SERVER");
+
+            try {
+                serverHttp.getResponse().setStatus(clientHttpRes.getStatus());
+                serverHttp.getResponse().getMessageBuffer().set(clientHttpRes.getMessageBuffer());
+                copyHeaders(clientHttpRes.getMimeHeaders(),
+                        serverHttp.getResponse().getMimeHeaders());
+
+                serverHttp.getResponse().getMimeHeaders().addValue("TomcatProxy").set("True");
+
+                clientHttpReq.getHttpChannel().setDataReceivedCallback(copy);
+                copy.handleReceived(clientHttpReq.getHttpChannel());
+
+                serverHttp.startSending();
+
+
+                //clientHttpReq.flush(); // send any data still there
+
+                //  if (clientHttpReq.getHttpChannel().getIn().isClosedAndEmpty()) {
+                //     serverHttp.getOut().close(); // all data from client already in buffers
+                //  }
+
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+    }
+
+    static final class HttpDoneCallback implements RequestCompleted {
+
+        public HttpDoneCallback() {
+        }
+
+        @Override
+        public void handle(HttpChannel doneCh, Object extraData) throws IOException {
+            HttpChannel serverCh =
+                (HttpChannel) doneCh.getRequest().getAttribute("SERVER");
+            HttpChannel clientCh = doneCh;
+            String tgt = "C";
+            if (serverCh == null) {
+                 serverCh = doneCh;
+                 clientCh =
+                    (HttpChannel) doneCh.getRequest().getAttribute("CLIENT");
+                 tgt = "S";
+            }
+            if (serverCh == null || clientCh == null) {
+                return;
+            }
+            if (doneCh.getError()) {
+                serverCh.abort("Proxy error");
+                clientCh.abort("Proxy error");
+                return;
+            }
+
+            if (log.isLoggable(Level.FINE)) {
+                HttpChannel peerCh =
+                    (HttpChannel) doneCh.getRequest().getAttribute("SERVER");
+                if (peerCh == null) {
+                    peerCh =
+                        (HttpChannel) doneCh.getRequest().getAttribute("CLIENT");
+                } else {
+
+                }
+                log.info(tgt + " " + peerCh.getId() + " " +
+                        doneCh.getTarget() + " " +
+                        doneCh.getRequest().getMethod() +
+                        " " + doneCh.getRequest().getRequestURI() + " " +
+                        doneCh.getResponse().getStatus() + " IN:" + doneCh.getIn()
+                        + " OUT:" + doneCh.getOut() +
+                        " SIN:" + peerCh.getIn() +
+                        " SOUT:" + peerCh.getOut() );
+            }
+            // stop forwarding. After this call the client object will be
+            // recycled
+            //clientCB.outBuffer = null;
+
+            // We must releaes both at same time
+            synchronized (this) {
+
+                serverCh.complete();
+
+                if (clientCh.getRequest().getAttribute("SERVER") == null) {
+                    return;
+                }
+                if (clientCh.isDone() && serverCh.isDone()) {
+                    clientCh.getRequest().setAttribute("SERVER", null);
+                    serverCh.getRequest().setAttribute("CLIENT", null);
+                    clientCh.getRequest().setAttribute("P", null);
+                    serverCh.getRequest().setAttribute("P", null);
+                    // Reuse the objects.
+                    serverCh.release();
+                    clientCh.release();
+                }
+            }
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/ProxyFlushedCallback.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/ProxyFlushedCallback.java
new file mode 100644
index 0000000..63fb18b
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/ProxyFlushedCallback.java
@@ -0,0 +1,25 @@
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+
+public final class ProxyFlushedCallback implements IOConnector.DataFlushedCallback {
+    IOChannel peerCh;
+
+    public ProxyFlushedCallback(IOChannel ch2, IOChannel clientChannel2) {
+        peerCh = ch2;
+    }
+
+    @Override
+    public void handleFlushed(IOChannel ch) throws IOException {
+        if (ch.getOut().isClosedAndEmpty()) {
+            if (!peerCh.getOut().isAppendClosed()) {
+                peerCh.close();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/SocksServer.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/SocksServer.java
new file mode 100644
index 0000000..ad68345
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/SocksServer.java
@@ -0,0 +1,448 @@
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.SocketAddress;
+import java.nio.ByteBuffer;
+import java.nio.CharBuffer;
+import java.util.Timer;
+import java.util.TimerTask;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.SocketConnector;
+
+/**
+ * A test for the selector package, and helper for the proxy -
+ * a SOCKS4a server.
+ *
+ * Besides the connection initialization, it's almost the
+ *  same as the CONNECT method in http proxy.
+ *
+ * http://ftp.icm.edu.pl/packages/socks/socks4/SOCKS4.protocol
+ * http://www.smartftp.com/Products/SmartFTP/RFC/socks4a.protocol
+ * http://www.faqs.org/rfcs/rfc1928.html
+ * https://svn.torproject.org/svn/tor/trunk/doc/spec/socks-extensions.txt
+ *
+ * In firefox, set network.proxy.socks_remote_dns = true to do DNS via proxy.
+ *
+ * Also interesting:
+ * http://transocks.sourceforge.net/
+ *
+ * @author Costin Manolache
+ */
+public class SocksServer implements Runnable, IOConnector.ConnectedCallback {
+    protected int port = 2080;
+
+    protected IOConnector ioConnector;
+    protected static Logger log = Logger.getLogger("SocksServer");
+
+    protected long idleTimeout = 10 * 60000; // 10 min
+
+    protected long lastConnection = 0;
+    protected long totalConTime = 0;
+    protected AtomicInteger totalConnections = new AtomicInteger();
+
+    protected AtomicInteger active = new AtomicInteger();
+
+    protected long inBytes;
+    protected long outBytes;
+    protected static int sockets;
+
+    public int getPort() {
+        return port;
+    }
+
+    public int getActive() {
+        return active.get();
+    }
+
+    public int getTotal() {
+        return totalConnections.get();
+    }
+
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    public void handleAccepted(IOChannel accepted) throws IOException {
+        lastConnection = System.currentTimeMillis();
+        active.incrementAndGet();
+        totalConnections.incrementAndGet();
+        sockets++;
+
+        final SocksServerConnection socksCon = new SocksServerConnection(accepted);
+        socksCon.pool = ioConnector;
+        socksCon.server = this;
+
+        accepted.setDataReceivedCallback(socksCon);
+        socksCon.handleReceived(accepted);
+    }
+
+    /**
+     * Exit if no activity happens.
+     */
+    public void setIdleTimeout(long to) {
+        idleTimeout = to;
+    }
+
+    public long getIdleTimeout() {
+        return idleTimeout;
+    }
+
+    public void stop() {
+        ioConnector.stop();
+    }
+
+    public void initServer() throws IOException {
+        if (ioConnector == null) {
+            ioConnector = new SocketConnector();
+        }
+        ioConnector.acceptor(this, Integer.toString(port), null);
+
+        final Timer timer = new Timer(true /* daemon */);
+        timer.scheduleAtFixedRate(new TimerTask() {
+            @Override
+            public void run() {
+                try {
+                // if lastConnection == 0 - it'll terminate on first timer
+                float avg = (totalConnections.get() > 0) ?
+                        totalConTime / totalConnections.get() : 0;
+                System.err.println("Socks:"
+                        + "\ttotal=" + totalConnections
+                        + "\tin=" + inBytes
+                        + "\tout=" + outBytes
+                        + "\tavg=" + (int) avg);
+                if (active.get() <= 0
+                        && idleTimeout > 0
+                        && System.currentTimeMillis() - lastConnection > idleTimeout) {
+                    System.err.println("Idle timeout");
+                    stop();
+                    this.cancel();
+                    timer.cancel();
+                }
+                } catch (Throwable t) {
+                    log.log(Level.SEVERE, "Error in timer", t);
+                }
+            }
+        }, 5 * 60 * 1000, 5 * 60 * 1000); // 5
+
+
+    }
+
+
+    public static class SocksServerConnection implements IOConnector.DataReceivedCallback, IOConnector.ConnectedCallback {
+
+        protected SocksServer server;
+
+        boolean headReceived;
+        boolean head5Received = false;
+
+        ByteBuffer headBuffer = ByteBuffer.allocate(256);
+        ByteBuffer headReadBuffer = headBuffer.duplicate();
+
+        ByteBuffer headResBuffer = ByteBuffer.allocate(256);
+        IOConnector pool;
+        byte ver;
+        byte cmd;
+        long startTime = System.currentTimeMillis();
+
+        static final int CMD_CONNECT = 0;
+        static final byte CMD_RESOLVE = (byte) 0xF0;
+
+        int port;
+        byte[] hostB = new byte[4];
+        CharBuffer userId = CharBuffer.allocate(256);
+        CharBuffer hostName = CharBuffer.allocate(256);
+
+        SocketAddress sa = null;
+
+        private byte atyp;
+
+        IOChannel serverCh;
+
+        public SocksServerConnection(IOChannel accepted) {
+            this.serverCh = accepted;
+        }
+
+        protected void afterClientConnect(IOChannel clientCh) throws IOException {
+            headResBuffer.clear();
+            if (ver == 4) {
+                headResBuffer.put((byte) 0);
+                headResBuffer.put((byte) 90);
+                for (int i = 0; i < 6; i++ ) {
+                    headResBuffer.put((byte) 0);
+                }
+            } else {
+                headResBuffer.put((byte) 5);
+                headResBuffer.put((byte) 0);
+                headResBuffer.put((byte) 0);
+                headResBuffer.put((byte) 1); // ip
+
+                headResBuffer.put(hostB);
+                int port2 = (Integer) clientCh.getAttribute(IOChannel.ATT_REMOTE_PORT);
+                headResBuffer.putShort((short) port2);
+            }
+
+            headResBuffer.flip();
+
+            serverCh.getOut().queue(headResBuffer);
+            log.fine("Connected " + sa.toString());
+
+            if (headReadBuffer.remaining() > 0) {
+                serverCh.getOut().queue(headReadBuffer);
+            }
+            serverCh.startSending();
+        }
+
+        public void afterClose() {
+            long conTime = System.currentTimeMillis() - startTime;
+            int a = server.active.decrementAndGet();
+            if (a < 0) {
+                System.err.println("negative !!");
+                server.active.set(0);
+            }
+//            System.err.println(sa + "\tsR:" +
+//                    received
+//                    + "\tcR:" + clientReceived
+//                    + "\tactive:" + a
+//                    + "\ttotC:" + server.totalConnections
+//                    + "\ttime:" + conTime);
+//            server.inBytes += received;
+//            server.totalConTime += conTime;
+//            server.outBytes += clientReceived;
+        }
+
+
+        protected int parseHead() throws IOException {
+            // data is between 0 and pos.
+            int pos = headBuffer.position();
+            headReadBuffer.clear();
+            headReadBuffer.limit(pos);
+            if (headReadBuffer.remaining() < 2) {
+                return -1;
+            }
+
+            ByteBuffer bb = headReadBuffer;
+            ver = bb.get();
+            if (ver == 5) {
+                return parseHead5();
+            }
+            if (headReadBuffer.remaining() < 8) {
+                return -1;
+            }
+            cmd = bb.get();
+            port = bb.getShort();
+            bb.get(hostB);
+            userId.clear();
+            int rc = readStringZ(bb, userId);
+            // Mozilla userid: MOZ ...
+            if (rc == -1) {
+                return rc;
+            }
+            if (hostB[0] == 0 && hostB[1] == 0 && hostB[2] == 0) {
+                // 0.0.0.x
+                atyp = 3;
+                hostName.clear();
+                rc = readStringZ(bb, hostName);
+                if (rc == -1) {
+                    return rc;
+                }
+            } else {
+                atyp = 1;
+            }
+
+            headReceived = true;
+
+            return 4;
+        }
+
+        protected int parseHead5_2() throws IOException {
+            // data is between 0 and pos.
+            int pos = headBuffer.position();
+
+            headReadBuffer.clear();
+            headReadBuffer.limit(pos);
+
+            if (headReadBuffer.remaining() < 7) {
+                return -1;
+            }
+
+            ByteBuffer bb = headReadBuffer;
+            ver = bb.get();
+            cmd = bb.get();
+            bb.get(); // reserved
+            atyp = bb.get();
+            if (atyp == 1) {
+                bb.get(hostB);
+            } else if (atyp == 3) {
+                hostName.clear();
+                int rc = readStringN(bb, hostName);
+                if (rc == -1) {
+                    return rc;
+                }
+            } // ip6 not supported right now, easy to add
+
+            port = bb.getShort();
+
+            head5Received = true;
+
+            return 5;
+        }
+
+        private int parseHead5() {
+            ByteBuffer bb = headReadBuffer;
+            int nrMethods = ((int)bb.get()) & 0xFF;
+            if (bb.remaining() < nrMethods) {
+                return -1;
+            }
+            for (int i = 0; i < nrMethods; i++) {
+                // ignore
+                bb.get();
+            }
+            return 5;
+        }
+
+        private int readStringZ(ByteBuffer bb, CharBuffer bc) throws IOException {
+            bc.clear();
+            while (true) {
+                if (!bb.hasRemaining()) {
+                    return -1; // not complete
+                }
+                byte b = bb.get();
+                if (b == 0) {
+                    bc.flip();
+                    return 0;
+                } else {
+                    bc.put((char) b);
+                }
+            }
+        }
+
+        private int readStringN(ByteBuffer bb, CharBuffer bc) throws IOException {
+            bc.clear();
+            int len = ((int) bb.get()) & 0xff;
+            for (int i = 0; i < len; i++) {
+                if (!bb.hasRemaining()) {
+                    return -1; // not complete
+                }
+                byte b = bb.get();
+                bc.put((char) b);
+            }
+            bc.flip();
+            return len;
+        }
+
+        static ExecutorService connectTP = Executors.newCachedThreadPool();
+
+        protected void startClientConnection() throws IOException {
+            // TODO: use different thread ?
+            if (atyp == 3) {
+                connectTP.execute(new Runnable() {
+
+                    public void run() {
+                        try {
+                            sa = new InetSocketAddress(hostName.toString(), port);
+                            pool.connect(hostName.toString(), port,
+                                    SocksServerConnection.this);
+                        } catch (Exception ex) {
+                            log.severe("Error connecting");
+                        }
+                    }
+                });
+            } else {
+                InetAddress addr = InetAddress.getByAddress(hostB);
+                pool.connect(addr.toString(), port, this);
+            } // TODO: ip6
+        }
+
+        public void handleConnected(IOChannel ioch) throws IOException {
+            ioch.setDataReceivedCallback(new CopyCallback(serverCh));
+            //ioch.setDataFlushedCallback(new ProxyFlushedCallback(serverCh, ioch));
+
+            serverCh.setDataReceivedCallback(new CopyCallback(ioch));
+            //serverCh.setDataFlushedCallback(new ProxyFlushedCallback(ioch, serverCh));
+
+            afterClientConnect(ioch);
+
+            ioch.sendHandleReceivedCallback();
+        }
+
+
+        @Override
+        public void handleReceived(IOChannel net) throws IOException {
+            IOBuffer ch = net.getIn();
+            //SelectorChannel ch = (SelectorChannel) ioch;
+                if (!headReceived) {
+                    int rd = ch.read(headBuffer);
+                    if (rd == 0) {
+                        return;
+                    }
+                    if (rd == -1) {
+                        ch.close();
+                    }
+
+                    rd = parseHead();
+                    if (rd < 0) {
+                        return; // need more
+                    }
+                    if (rd == 5) {
+                        headResBuffer.clear();
+                        headResBuffer.put((byte) 5);
+                        headResBuffer.put((byte) 0);
+                        headResBuffer.flip();
+                        net.getOut().queue(headResBuffer);
+                        net.startSending();
+                        headReceived = true;
+                        headBuffer.clear();
+                        return;
+                    } else {
+                        headReceived = true;
+                        head5Received = true;
+                        startClientConnection();
+                    }
+                }
+
+                if (!head5Received) {
+                    int rd = ch.read(headBuffer);
+                    if (rd == 0) {
+                        return;
+                    }
+                    if (rd == -1) {
+                        ch.close();
+                    }
+
+                    rd = parseHead5_2();
+                    if (rd < 0) {
+                        return; // need more
+                    }
+
+                    startClientConnection();
+                }
+        }
+    }
+
+    @Override
+    public void run() {
+        try {
+            initServer();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    @Override
+    public void handleConnected(IOChannel ch) throws IOException {
+        handleAccepted(ch);
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java
new file mode 100644
index 0000000..e50e73e
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/proxy/StaticContentService.java
@@ -0,0 +1,129 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.proxy;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.BBucket;
+import org.apache.tomcat.lite.io.IOBuffer;
+
+/*
+ *
+ * Serve static content, from memory.
+ */
+public class StaticContentService implements HttpService  {
+    protected Logger log = Logger.getLogger("coyote.static");
+    protected BBucket mb;
+
+    protected boolean chunked = false;
+    int code = 200;
+
+    protected String contentType = "text/plain";
+
+
+    public StaticContentService() {
+    }
+
+    /**
+     * Used for testing chunked encoding.
+     * @return
+     */
+    public StaticContentService chunked() {
+      chunked = true;
+      return this;
+    }
+
+    public StaticContentService setData(byte[] data) {
+        mb = BBuffer.wrapper(data, 0, data.length);
+        return this;
+    }
+
+    public StaticContentService setStatus(int status) {
+        this.code = status;
+        return this;
+    }
+
+    public StaticContentService withLen(int len) {
+        byte[] data = new byte[len];
+        for (int i = 0; i < len; i++) {
+          data[i] = 'A';
+        }
+        mb = BBuffer.wrapper(data, 0, data.length);
+        return this;
+      }
+
+
+    public StaticContentService setData(CharSequence data) {
+      try {
+          IOBuffer tmp = new IOBuffer(null);
+          tmp.append(data);
+          mb = tmp.readAll(null);
+      } catch (IOException e) {
+      }
+      return this;
+    }
+
+    public StaticContentService setContentType(String ct) {
+      this.contentType = ct;
+      return this;
+    }
+
+    public void setFile(String path) {
+      try {
+        FileInputStream fis = new FileInputStream(path);
+        BBuffer bc = BBuffer.allocate(4096);
+
+        byte b[] = new byte[4096];
+        int rd = 0;
+        while ((rd = fis.read(b)) > 0) {
+            bc.append(b, 0, rd);
+        }
+        mb = bc;
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      }
+    }
+
+    @Override
+    public void service(HttpRequest httpReq, HttpResponse res) throws IOException {
+
+        res.setStatus(code);
+
+          if (!chunked) {
+            res.setContentLength(mb.remaining());
+          }
+          res.setContentType(contentType);
+
+          int len = mb.remaining();
+          int first = 0;
+
+          if (chunked) {
+              first = len / 2;
+              res.getBody()
+                  .queue(BBuffer.wrapper(mb, 0, first));
+              res.flush();
+          }
+
+          res.getBody().queue(BBuffer.wrapper(mb, 0, len - first));
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java
new file mode 100644
index 0000000..0623e8d
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/service/IOStatus.java
@@ -0,0 +1,56 @@
+/*
+ */
+package org.apache.tomcat.lite.service;
+
+import java.io.IOException;
+import java.util.List;
+
+import org.apache.tomcat.lite.http.HttpConnectionPool;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpWriter;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer;
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.io.IOChannel;
+
+/**
+ * Dump status of a connection pool.
+ */
+public class IOStatus implements HttpService {
+
+    private HttpConnectionPool pool;
+
+    public IOStatus(HttpConnectionPool pool) {
+        this.pool = pool;
+    }
+
+    @Override
+    public void service(HttpRequest httpReq, HttpResponse httpRes)
+            throws IOException {
+        HttpConnectionPool sc = pool;
+        HttpWriter out = httpRes.getBodyWriter();
+
+        httpRes.setContentType("text/plain");
+        // TODO: use JMX/DynamicObject to get all public info
+        out.println("hosts=" + sc.getTargetCount());
+        out.println("waiting=" + sc.getSocketCount());
+        out.println("closed=" + sc.getClosedSockets());
+        out.println();
+
+        for (RemoteServer remote: sc.getServers()) {
+            out.append(remote.target);
+            out.append("=");
+            List<HttpConnection> connections = remote.getConnections();
+            out.println(Integer.toString(connections.size()));
+
+            for (IOChannel ch: connections) {
+                out.println(ch.getId() +
+                        " " + ch.toString());
+            }
+            out.println();
+        }
+
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/service/LogConfig.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/service/LogConfig.java
new file mode 100644
index 0000000..b3b99a8
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/service/LogConfig.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2004 Costin Manolache
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.service;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+
+/**
+ * Log configuration
+ *
+ */
+public class LogConfig implements HttpService {
+
+    /**
+     * Framework can set this attribute with comma separated
+     * list of loggers to set to debug level.
+     * This is used at startup.
+     */
+    public void setDebug(String debug) {
+        for (String log : debug.split(",")) {
+            Logger logger = Logger.getLogger(log);
+            logger.setLevel(Level.INFO);
+        }
+    }
+
+    /**
+     *
+     */
+    public void setWarn(String nodebug) {
+        for (String log : nodebug.split(",")) {
+            Logger logger = Logger.getLogger(log);
+            logger.setLevel(Level.WARNING);
+        }
+    }
+
+    @Override
+    public void service(HttpRequest httpReq, HttpResponse httpRes)
+            throws IOException {
+        String debug = httpReq.getParameter("debug");
+        setDebug(debug);
+        String warn = httpReq.getParameter("warn");
+        setWarn(warn);
+    }
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java
new file mode 100644
index 0000000..5f76152
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/FastHttpDateFormat.java
@@ -0,0 +1,231 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.util;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Utility class to generate HTTP dates.
+ *
+ * @author Remy Maucherat
+ */
+public final class FastHttpDateFormat {
+
+
+    // -------------------------------------------------------------- Variables
+
+
+    protected static final int CACHE_SIZE =
+        Integer.parseInt(System.getProperty("org.apache.tomcat.util.http.FastHttpDateFormat.CACHE_SIZE", "1000"));
+
+
+    /**
+     * HTTP date format.
+     */
+    protected static final SimpleDateFormat format =
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
+
+
+    /**
+     * The set of SimpleDateFormat formats to use in getDateHeader().
+     */
+    protected static final SimpleDateFormat formats[] = {
+        new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+        new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+    };
+
+
+    protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+
+
+    /**
+     * GMT timezone - all HTTP dates are on GMT
+     */
+    static {
+
+        format.setTimeZone(gmtZone);
+
+        formats[0].setTimeZone(gmtZone);
+        formats[1].setTimeZone(gmtZone);
+        formats[2].setTimeZone(gmtZone);
+
+    }
+
+
+    /**
+     * Instant on which the currentDate object was generated.
+     */
+    protected static long currentDateGenerated = 0L;
+
+
+    /**
+     * Current formatted date.
+     */
+    protected static String currentDate = null;
+
+
+    /**
+     * Formatter cache.
+     */
+    protected static final ConcurrentHashMap<Long, String> formatCache =
+        new ConcurrentHashMap<Long, String>(CACHE_SIZE);
+
+
+    /**
+     * Parser cache.
+     */
+    protected static final ConcurrentHashMap<String, Long> parseCache =
+        new ConcurrentHashMap<String, Long>(CACHE_SIZE);
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Get the current date in HTTP format.
+     */
+    public static final String getCurrentDate() {
+
+        long now = System.currentTimeMillis();
+        if ((now - currentDateGenerated) > 1000) {
+            synchronized (format) {
+                if ((now - currentDateGenerated) > 1000) {
+                    currentDateGenerated = now;
+                    currentDate = format.format(new Date(now));
+                }
+            }
+        }
+        return currentDate;
+
+    }
+
+
+    /**
+     * Get the HTTP format of the specified date.
+     */
+    public static final String formatDate
+        (long value, DateFormat threadLocalformat) {
+
+        Long longValue = new Long(value);
+        String cachedDate = formatCache.get(longValue);
+        if (cachedDate != null)
+            return cachedDate;
+
+        String newDate = null;
+        Date dateValue = new Date(value);
+        if (threadLocalformat != null) {
+            newDate = threadLocalformat.format(dateValue);
+            updateFormatCache(longValue, newDate);
+        } else {
+            synchronized (formatCache) {
+                synchronized (format) {
+                    newDate = format.format(dateValue);
+                }
+                updateFormatCache(longValue, newDate);
+            }
+        }
+        return newDate;
+
+    }
+
+
+    /**
+     * Try to parse the given date as a HTTP date.
+     */
+    public static final long parseDate(String value,
+                                       DateFormat[] threadLocalformats) {
+
+        Long cachedDate = parseCache.get(value);
+        if (cachedDate != null)
+            return cachedDate.longValue();
+
+        Long date = null;
+        if (threadLocalformats != null) {
+            date = internalParseDate(value, threadLocalformats);
+            updateParseCache(value, date);
+        } else {
+            synchronized (parseCache) {
+                date = internalParseDate(value, formats);
+                updateParseCache(value, date);
+            }
+        }
+        if (date == null) {
+            return (-1L);
+        } else {
+            return date.longValue();
+        }
+
+    }
+
+
+    /**
+     * Parse date with given formatters.
+     */
+    private static final Long internalParseDate
+        (String value, DateFormat[] formats) {
+        Date date = null;
+        for (int i = 0; (date == null) && (i < formats.length); i++) {
+            try {
+                date = formats[i].parse(value);
+            } catch (ParseException e) {
+                ;
+            }
+        }
+        if (date == null) {
+            return null;
+        }
+        return new Long(date.getTime());
+    }
+
+
+    /**
+     * Update cache.
+     */
+    private static void updateFormatCache(Long key, String value) {
+        if (value == null) {
+            return;
+        }
+        if (formatCache.size() > CACHE_SIZE) {
+            formatCache.clear();
+        }
+        formatCache.put(key, value);
+    }
+
+
+    /**
+     * Update cache.
+     */
+    private static void updateParseCache(String key, Long value) {
+        if (value == null) {
+            return;
+        }
+        if (parseCache.size() > CACHE_SIZE) {
+            parseCache.clear();
+        }
+        parseCache.put(key, value);
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java
new file mode 100644
index 0000000..cdd3718
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/LocaleParser.java
@@ -0,0 +1,394 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.util;
+
+import java.util.ArrayList;
+import java.util.Locale;
+import java.util.TreeMap;
+
+
+/**
+ * Utility class for string parsing that is higher performance than
+ * StringParser for simple delimited text cases.  Parsing is performed
+ * by setting the string, and then using the <code>findXxxx()</code> and
+ * <code>skipXxxx()</code> families of methods to remember significant
+ * offsets.  To retrieve the parsed substrings, call the <code>extract()</code>
+ * method with the appropriate saved offset values.
+ *
+ * @author Craig R. McClanahan
+ */
+public final class LocaleParser {
+
+    public LocaleParser() {
+        this(null);
+    }
+
+    public LocaleParser(String string) {
+        setString(string);
+    }
+
+    public TreeMap parseLocale(String value) {
+      // Store the accumulated languages that have been requested in
+      // a local collection, sorted by the quality value (so we can
+      // add Locales in descending order).  The values will be ArrayLists
+      // containing the corresponding Locales to be added
+      TreeMap locales = new TreeMap();
+
+      // Preprocess the value to remove all whitespace
+      int white = value.indexOf(' ');
+      if (white < 0)
+          white = value.indexOf('\t');
+      if (white >= 0) {
+          StringBuilder sb = new StringBuilder();
+          int len = value.length();
+          for (int i = 0; i < len; i++) {
+              char ch = value.charAt(i);
+              if ((ch != ' ') && (ch != '\t'))
+                  sb.append(ch);
+          }
+          value = sb.toString();
+      }
+
+      LocaleParser parser = this;
+      // Process each comma-delimited language specification
+      parser.setString(value);        // ASSERT: parser is available to us
+      int length = parser.getLength();
+      while (true) {
+
+          // Extract the next comma-delimited entry
+          int start = parser.getIndex();
+          if (start >= length)
+              break;
+          int end = parser.findChar(',');
+          String entry = parser.extract(start, end).trim();
+          parser.advance();   // For the following entry
+
+          // Extract the quality factor for this entry
+          double quality = 1.0;
+          int semi = entry.indexOf(";q=");
+          if (semi >= 0) {
+              try {
+                  quality = Double.parseDouble(entry.substring(semi + 3));
+              } catch (NumberFormatException e) {
+                  quality = 0.0;
+              }
+              entry = entry.substring(0, semi);
+          }
+
+          // Skip entries we are not going to keep track of
+          if (quality < 0.00005)
+              continue;       // Zero (or effectively zero) quality factors
+          if ("*".equals(entry))
+              continue;       // FIXME - "*" entries are not handled
+
+          // Extract the language and country for this entry
+          String language = null;
+          String country = null;
+          String variant = null;
+          int dash = entry.indexOf('-');
+          if (dash < 0) {
+              language = entry;
+              country = "";
+              variant = "";
+          } else {
+              language = entry.substring(0, dash);
+              country = entry.substring(dash + 1);
+              int vDash = country.indexOf('-');
+              if (vDash > 0) {
+                  String cTemp = country.substring(0, vDash);
+                  variant = country.substring(vDash + 1);
+                  country = cTemp;
+              } else {
+                  variant = "";
+              }
+          }
+
+          // Add a new Locale to the list of Locales for this quality level
+          Locale locale = new Locale(language, country, variant);
+          Double key = new Double(-quality);  // Reverse the order
+          ArrayList values = (ArrayList) locales.get(key);
+          if (values == null) {
+              values = new ArrayList();
+              locales.put(key, values);
+          }
+          values.add(locale);
+
+      }
+
+      return locales;
+    }
+
+    /**
+     * The characters of the current string, as a character array.  Stored
+     * when the string is first specified to speed up access to characters
+     * being compared during parsing.
+     */
+    private char chars[] = null;
+
+
+    /**
+     * The zero-relative index of the current point at which we are
+     * positioned within the string being parsed.  <strong>NOTE</strong>:
+     * the value of this index can be one larger than the index of the last
+     * character of the string (i.e. equal to the string length) if you
+     * parse off the end of the string.  This value is useful for extracting
+     * substrings that include the end of the string.
+     */
+    private int index = 0;
+
+
+    /**
+     * The length of the String we are currently parsing.  Stored when the
+     * string is first specified to avoid repeated recalculations.
+     */
+    private int length = 0;
+
+
+    /**
+     * The String we are currently parsing.
+     */
+    private String string = null;
+
+
+    // ------------------------------------------------------------- Properties
+
+
+    /**
+     * Return the zero-relative index of our current parsing position
+     * within the string being parsed.
+     */
+    public int getIndex() {
+
+        return (this.index);
+
+    }
+
+
+    /**
+     * Return the length of the string we are parsing.
+     */
+    public int getLength() {
+
+        return (this.length);
+
+    }
+
+
+    /**
+     * Return the String we are currently parsing.
+     */
+    public String getString() {
+
+        return (this.string);
+
+    }
+
+
+    /**
+     * Set the String we are currently parsing.  The parser state is also reset
+     * to begin at the start of this string.
+     *
+     * @param string The string to be parsed.
+     */
+    public void setString(String string) {
+
+        this.string = string;
+        if (string != null) {
+            this.length = string.length();
+            chars = this.string.toCharArray();
+        } else {
+            this.length = 0;
+            chars = new char[0];
+        }
+        reset();
+
+    }
+
+
+    // --------------------------------------------------------- Public Methods
+
+
+    /**
+     * Advance the current parsing position by one, if we are not already
+     * past the end of the string.
+     */
+    public void advance() {
+
+        if (index < length)
+            index++;
+
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and extends to the end of the string being parsed.  If this is not
+     * possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     */
+    public String extract(int start) {
+
+        if ((start < 0) || (start >= length))
+            return ("");
+        else
+            return (string.substring(start));
+
+    }
+
+
+    /**
+     * Extract and return a substring that starts at the specified position,
+     * and ends at the character before the specified position.  If this is
+     * not possible, a zero-length string is returned.
+     *
+     * @param start Starting index, zero relative, inclusive
+     * @param end Ending index, zero relative, exclusive
+     */
+    public String extract(int start, int end) {
+
+        if ((start < 0) || (start >= end) || (end > length))
+            return ("");
+        else
+            return (string.substring(start, end));
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of the specified character,
+     * or the index of the character after the last position of the string
+     * if no more occurrences of this character are found.  The current
+     * parsing position is updated to the returned value.
+     *
+     * @param ch Character to be found
+     */
+    public int findChar(char ch) {
+
+        while ((index < length) && (ch != chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of a non-whitespace character,
+     * or the index of the character after the last position of the string
+     * if no more non-whitespace characters are found.  The current
+     * parsing position is updated to the returned value.
+     */
+    public int findText() {
+
+        while ((index < length) && isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Return the index of the next occurrence of a whitespace character,
+     * or the index of the character after the last position of the string
+     * if no more whitespace characters are found.  The current parsing
+     * position is updated to the returned value.
+     */
+    public int findWhite() {
+
+        while ((index < length) && !isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Reset the current state of the parser to the beginning of the
+     * current string being parsed.
+     */
+    public void reset() {
+
+        index = 0;
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at the
+     * specified character, or until it moves past the end of the string.
+     * Return the final value.
+     *
+     * @param ch Character to be skipped
+     */
+    public int skipChar(char ch) {
+
+        while ((index < length) && (ch == chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at a
+     * non-whitespace character, or until it moves past the end of the string.
+     * Return the final value.
+     */
+    public int skipText() {
+
+        while ((index < length) && !isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    /**
+     * Advance the current parsing position while it is pointing at a
+     * whitespace character, or until it moves past the end of the string.
+     * Return the final value.
+     */
+    public int skipWhite() {
+
+        while ((index < length) && isWhite(chars[index]))
+            index++;
+        return (index);
+
+    }
+
+
+    // ------------------------------------------------------ Protected Methods
+
+
+    /**
+     * Is the specified character considered to be whitespace?
+     *
+     * @param ch Character to be checked
+     */
+    protected boolean isWhite(char ch) {
+
+        if ((ch == ' ') || (ch == '\t') || (ch == '\r') || (ch == '\n'))
+            return (true);
+        else
+            return (false);
+
+    }
+
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java
new file mode 100644
index 0000000..11828ba
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/MimeMap.java
@@ -0,0 +1,195 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.util;
+
+import java.net.FileNameMap;
+import java.util.Enumeration;
+import java.util.Hashtable;
+
+
+/**
+ * A mime type map that implements the java.net.FileNameMap interface.
+ *
+ * @author James Duncan Davidson [duncan@eng.sun.com]
+ * @author Jason Hunter [jch@eng.sun.com]
+ */
+public class MimeMap implements FileNameMap {
+
+    // Defaults - all of them are "well-known" types,
+    // you can add using normal web.xml.
+
+    public static Hashtable<String,String> defaultMap =
+        new Hashtable<String,String>(101);
+    static {
+        defaultMap.put("txt", "text/plain");
+        defaultMap.put("css", "text/css");
+        defaultMap.put("html","text/html");
+        defaultMap.put("htm", "text/html");
+        defaultMap.put("gif", "image/gif");
+        defaultMap.put("jpg", "image/jpeg");
+        defaultMap.put("jpe", "image/jpeg");
+        defaultMap.put("jpeg", "image/jpeg");
+        defaultMap.put("png", "image/png");
+                defaultMap.put("java", "text/plain");
+        defaultMap.put("body", "text/html");
+        defaultMap.put("rtx", "text/richtext");
+        defaultMap.put("tsv", "text/tab-separated-values");
+        defaultMap.put("etx", "text/x-setext");
+        defaultMap.put("ps", "application/x-postscript");
+        defaultMap.put("class", "application/java");
+        defaultMap.put("csh", "application/x-csh");
+        defaultMap.put("sh", "application/x-sh");
+        defaultMap.put("tcl", "application/x-tcl");
+        defaultMap.put("tex", "application/x-tex");
+        defaultMap.put("texinfo", "application/x-texinfo");
+        defaultMap.put("texi", "application/x-texinfo");
+        defaultMap.put("t", "application/x-troff");
+        defaultMap.put("tr", "application/x-troff");
+        defaultMap.put("roff", "application/x-troff");
+        defaultMap.put("man", "application/x-troff-man");
+        defaultMap.put("me", "application/x-troff-me");
+        defaultMap.put("ms", "application/x-wais-source");
+        defaultMap.put("src", "application/x-wais-source");
+        defaultMap.put("zip", "application/zip");
+        defaultMap.put("bcpio", "application/x-bcpio");
+        defaultMap.put("cpio", "application/x-cpio");
+        defaultMap.put("gtar", "application/x-gtar");
+        defaultMap.put("shar", "application/x-shar");
+        defaultMap.put("sv4cpio", "application/x-sv4cpio");
+        defaultMap.put("sv4crc", "application/x-sv4crc");
+        defaultMap.put("tar", "application/x-tar");
+        defaultMap.put("ustar", "application/x-ustar");
+        defaultMap.put("dvi", "application/x-dvi");
+        defaultMap.put("hdf", "application/x-hdf");
+        defaultMap.put("latex", "application/x-latex");
+        defaultMap.put("bin", "application/octet-stream");
+        defaultMap.put("oda", "application/oda");
+        defaultMap.put("pdf", "application/pdf");
+        defaultMap.put("ps", "application/postscript");
+        defaultMap.put("eps", "application/postscript");
+        defaultMap.put("ai", "application/postscript");
+        defaultMap.put("rtf", "application/rtf");
+        defaultMap.put("nc", "application/x-netcdf");
+        defaultMap.put("cdf", "application/x-netcdf");
+        defaultMap.put("cer", "application/x-x509-ca-cert");
+        defaultMap.put("exe", "application/octet-stream");
+        defaultMap.put("gz", "application/x-gzip");
+        defaultMap.put("Z", "application/x-compress");
+        defaultMap.put("z", "application/x-compress");
+        defaultMap.put("hqx", "application/mac-binhex40");
+        defaultMap.put("mif", "application/x-mif");
+        defaultMap.put("ief", "image/ief");
+        defaultMap.put("tiff", "image/tiff");
+        defaultMap.put("tif", "image/tiff");
+        defaultMap.put("ras", "image/x-cmu-raster");
+        defaultMap.put("pnm", "image/x-portable-anymap");
+        defaultMap.put("pbm", "image/x-portable-bitmap");
+        defaultMap.put("pgm", "image/x-portable-graymap");
+        defaultMap.put("ppm", "image/x-portable-pixmap");
+        defaultMap.put("rgb", "image/x-rgb");
+        defaultMap.put("xbm", "image/x-xbitmap");
+        defaultMap.put("xpm", "image/x-xpixmap");
+        defaultMap.put("xwd", "image/x-xwindowdump");
+        defaultMap.put("au", "audio/basic");
+        defaultMap.put("snd", "audio/basic");
+        defaultMap.put("aif", "audio/x-aiff");
+        defaultMap.put("aiff", "audio/x-aiff");
+        defaultMap.put("aifc", "audio/x-aiff");
+        defaultMap.put("wav", "audio/x-wav");
+        defaultMap.put("mpeg", "video/mpeg");
+        defaultMap.put("mpg", "video/mpeg");
+        defaultMap.put("mpe", "video/mpeg");
+        defaultMap.put("qt", "video/quicktime");
+        defaultMap.put("mov", "video/quicktime");
+        defaultMap.put("avi", "video/x-msvideo");
+        defaultMap.put("movie", "video/x-sgi-movie");
+        defaultMap.put("avx", "video/x-rad-screenplay");
+        defaultMap.put("wrl", "x-world/x-vrml");
+        defaultMap.put("mpv2", "video/mpeg2");
+
+        /* Add XML related MIMEs */
+
+        defaultMap.put("xml", "text/xml");
+        defaultMap.put("xsl", "text/xml");
+        defaultMap.put("svg", "image/svg+xml");
+        defaultMap.put("svgz", "image/svg+xml");
+        defaultMap.put("wbmp", "image/vnd.wap.wbmp");
+        defaultMap.put("wml", "text/vnd.wap.wml");
+        defaultMap.put("wmlc", "application/vnd.wap.wmlc");
+        defaultMap.put("wmls", "text/vnd.wap.wmlscript");
+        defaultMap.put("wmlscriptc", "application/vnd.wap.wmlscriptc");
+    }
+
+
+    private Hashtable<String,String> map = new Hashtable<String,String>();
+
+    public void addContentType(String extn, String type) {
+        map.put(extn, type.toLowerCase());
+    }
+
+    public Enumeration getExtensions() {
+        return map.keys();
+    }
+
+    public String getMimeType(String ext) {
+        return getContentTypeFor(ext);
+    }
+
+    public String getContentType(String extn) {
+        String type = (String)map.get(extn.toLowerCase());
+        if( type == null ) type=(String)defaultMap.get( extn );
+        return type;
+    }
+
+    public void removeContentType(String extn) {
+        map.remove(extn.toLowerCase());
+    }
+
+    /** Get extension of file, without fragment id
+     */
+    public static String getExtension( String fileName ) {
+        // play it safe and get rid of any fragment id
+        // that might be there
+        int length=fileName.length();
+
+        int newEnd = fileName.lastIndexOf('#');
+        if( newEnd== -1 ) newEnd=length;
+        // Instead of creating a new string.
+        //         if (i != -1) {
+        //             fileName = fileName.substring(0, i);
+        //         }
+        int i = fileName.lastIndexOf('.', newEnd );
+        if (i != -1) {
+             return  fileName.substring(i + 1, newEnd );
+        } else {
+            // no extension, no content type
+            return null;
+        }
+    }
+
+    public String getContentTypeFor(String fileName) {
+        String extn=getExtension( fileName );
+        if (extn!=null) {
+            return getContentType(extn);
+        } else {
+            // no extension, no content type
+            return null;
+        }
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java
new file mode 100644
index 0000000..210a827
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/Range.java
@@ -0,0 +1,160 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.StringTokenizer;
+
+/**
+ * Utils to process HTTP/1.1 ranges. Used by default servlet, could
+ * be used by any servlet that needs to deal with ranges.
+ *
+ * It is very good to support ranges if you have large content. In most
+ * cases supporting one range is enough - getting multiple ranges doesn't
+ * seem very common, and it's complex (multipart response).
+ *
+ * @author Costin Manolache
+ * @author Remy Maucherat
+ * @author - see DefaultServlet in Catalin for other contributors
+ */
+public class Range {
+
+    public long start;
+    public long end;
+    public long length;
+
+    /**
+     * Validate range.
+     */
+    public boolean validate() {
+        if (end >= length)
+            end = length - 1;
+        return ( (start >= 0) && (end >= 0) && (start <= end)
+                 && (length > 0) );
+    }
+
+    public void recycle() {
+        start = 0;
+        end = 0;
+        length = 0;
+    }
+
+    /** Parse ranges.
+     *
+     * @return null if the range is invalid or can't be parsed
+     */
+    public static ArrayList parseRanges(long fileLength,
+                                        String rangeHeader) throws IOException {
+        ArrayList result = new ArrayList();
+        StringTokenizer commaTokenizer = new StringTokenizer(rangeHeader, ",");
+
+        // Parsing the range list
+        while (commaTokenizer.hasMoreTokens()) {
+            String rangeDefinition = commaTokenizer.nextToken().trim();
+
+            Range currentRange = new Range();
+            currentRange.length = fileLength;
+
+            int dashPos = rangeDefinition.indexOf('-');
+
+            if (dashPos == -1) {
+                return null;
+            }
+
+            if (dashPos == 0) {
+                try {
+                    long offset = Long.parseLong(rangeDefinition);
+                    currentRange.start = fileLength + offset;
+                    currentRange.end = fileLength - 1;
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+            } else {
+
+                try {
+                    currentRange.start = Long.parseLong
+                        (rangeDefinition.substring(0, dashPos));
+                    if (dashPos < rangeDefinition.length() - 1)
+                        currentRange.end = Long.parseLong
+                            (rangeDefinition.substring
+                             (dashPos + 1, rangeDefinition.length()));
+                    else
+                        currentRange.end = fileLength - 1;
+                } catch (NumberFormatException e) {
+                    return null;
+                }
+
+            }
+            if (!currentRange.validate()) {
+                return null;
+            }
+            result.add(currentRange);
+        }
+        return result;
+    }
+
+
+    /**
+     * Parse the Content-Range header. Used with PUT or in response.
+     *
+     * @return Range
+     */
+    public static Range parseContentRange(String rangeHeader)
+            throws IOException {
+        if (rangeHeader == null)
+            return null;
+
+        // bytes is the only range unit supported
+        if (!rangeHeader.startsWith("bytes")) {
+            return null;
+        }
+
+        rangeHeader = rangeHeader.substring(6).trim();
+
+        int dashPos = rangeHeader.indexOf('-');
+        int slashPos = rangeHeader.indexOf('/');
+
+        if (dashPos == -1) {
+            return null;
+        }
+
+        if (slashPos == -1) {
+            return null;
+        }
+
+        Range range = new Range();
+
+        try {
+            range.start = Long.parseLong(rangeHeader.substring(0, dashPos));
+            range.end =
+                Long.parseLong(rangeHeader.substring(dashPos + 1, slashPos));
+            range.length = Long.parseLong
+                (rangeHeader.substring(slashPos + 1, rangeHeader.length()));
+        } catch (NumberFormatException e) {
+            return null;
+        }
+
+        if (!range.validate()) {
+            return null;
+        }
+
+        return range;
+    }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java
new file mode 100644
index 0000000..f946e56
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/URLEncoder.java
@@ -0,0 +1,227 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.util;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.UnsupportedEncodingException;
+import java.util.BitSet;
+
+/**
+ *
+ * This class is very similar to the java.net.URLEncoder class.
+ *
+ * Unfortunately, with java.net.URLEncoder there is no way to specify to the
+ * java.net.URLEncoder which characters should NOT be encoded.
+ *
+ * This code was moved from DefaultServlet.java
+ *
+ * @author Craig R. McClanahan
+ * @author Remy Maucherat
+ */
+public class URLEncoder {
+    protected static final char[] hexadecimal =
+    {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+     'A', 'B', 'C', 'D', 'E', 'F'};
+
+    //Array containing the safe characters set.
+    protected BitSet safeChars = new BitSet(128);
+
+    public URLEncoder() {
+        for (char i = 'a'; i <= 'z'; i++) {
+            addSafeCharacter(i);
+        }
+        for (char i = 'A'; i <= 'Z'; i++) {
+            addSafeCharacter(i);
+        }
+        for (char i = '0'; i <= '9'; i++) {
+            addSafeCharacter(i);
+        }
+        //safe
+        safeChars.set('$');
+        safeChars.set('-');
+        safeChars.set('_');
+        safeChars.set('.');
+
+        // Dangerous: someone may treat this as " "
+        // RFC1738 does allow it, it's not reserved
+        //    safeChars.set('+');
+        //extra
+        safeChars.set('!');
+        safeChars.set('*');
+        safeChars.set('\'');
+        safeChars.set('(');
+        safeChars.set(')');
+        safeChars.set(',');
+    }
+
+    public void addSafeCharacter( char c ) {
+        safeChars.set( c );
+    }
+
+    public String encodeURL(String path) {
+        return encodeURL(path, "UTF-8", true);
+    }
+
+    public String encodeURL(String path, String enc, boolean allowSlash) {
+        int maxBytesPerChar = 10;
+
+        StringBuffer rewrittenPath = new StringBuffer(path.length());
+        ByteArrayOutputStream buf = new ByteArrayOutputStream(maxBytesPerChar);
+        OutputStreamWriter writer = null;
+        try {
+            writer = new OutputStreamWriter(buf, enc);
+        } catch (UnsupportedEncodingException e1) {
+            // shouldn't happen.
+        }
+
+        for (int i = 0; i < path.length(); i++) {
+            int c = (int) path.charAt(i);
+            if (c < 128 && safeChars.get(c) || allowSlash && c == '/') {
+                rewrittenPath.append((char)c);
+            } else {
+                // convert to external encoding before hex conversion
+                try {
+                    writer.write((char)c);
+                    if (c >= 0xD800 && c <= 0xDBFF) {
+                        if ( (i+1) < path.length()) {
+                            int d = path.charAt(i+1);
+                            if (d >= 0xDC00 && d <= 0xDFFF) {
+                                writer.write((char) d);
+                                i++;
+                            }
+                        }
+                    }
+                    writer.flush();
+                } catch(IOException e) {
+                    buf.reset();
+                    continue;
+                }
+                byte[] ba = buf.toByteArray();
+                for (int j = 0; j < ba.length; j++) {
+                    // Converting each byte in the buffer
+                    byte toEncode = ba[j];
+                    rewrittenPath.append('%');
+                    int low = (int) (toEncode & 0x0f);
+                    int high = (int) ((toEncode & 0xf0) >> 4);
+                    rewrittenPath.append(hexadecimal[high]);
+                    rewrittenPath.append(hexadecimal[low]);
+                }
+                buf.reset();
+            }
+        }
+        return rewrittenPath.toString();
+    }
+
+    /**
+     * Decode and return the specified URL-encoded String.
+     *
+     * @param str The url-encoded string
+     * @param enc The encoding to use; if null, the default encoding is used
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str, String enc) {
+
+        if (str == null)
+            return (null);
+
+        // use the specified encoding to extract bytes out of the
+        // given string so that the encoding is not lost. If an
+        // encoding is not specified, let it use platform default
+        byte[] bytes = null;
+        try {
+            if (enc == null) {
+                bytes = str.getBytes();
+            } else {
+                bytes = str.getBytes(enc);
+            }
+        } catch (UnsupportedEncodingException uee) {}
+
+        return URLDecode(bytes, enc);
+
+    }
+
+
+    /**
+     * Decode and return the specified URL-encoded String.
+     * When the byte array is converted to a string, the system default
+     * character encoding is used...  This may be different than some other
+     * servers.
+     *
+     * @param str The url-encoded string
+     *
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    public static String URLDecode(String str) {
+
+        return URLDecode(str, null);
+
+    }
+
+    /**
+     * Decode and return the specified URL-encoded byte array.
+     *
+     * @param bytes The url-encoded byte array
+     * @param enc The encoding to use; if null, the default encoding is used
+     * @exception IllegalArgumentException if a '%' character is not followed
+     * by a valid 2-digit hexadecimal number
+     */
+    private static String URLDecode(byte[] bytes, String enc) {
+
+        if (bytes == null)
+            return (null);
+
+        int len = bytes.length;
+        int ix = 0;
+        int ox = 0;
+        while (ix < len) {
+            byte b = bytes[ix++];     // Get byte to test
+            if (b == '+') {
+                b = (byte)' ';
+            } else if (b == '%') {
+                b = (byte) ((convertHexDigit(bytes[ix++]) << 4)
+                            + convertHexDigit(bytes[ix++]));
+            }
+            bytes[ox++] = b;
+        }
+        if (enc != null) {
+            try {
+                return new String(bytes, 0, ox, enc);
+            } catch (Exception e) {
+                e.printStackTrace();
+            }
+        }
+        return new String(bytes, 0, ox);
+
+    }
+
+    /**
+     * Convert a byte character value to hexidecimal digit value.
+     *
+     * @param b the character value byte
+     */
+    private static byte convertHexDigit( byte b ) {
+        if ((b >= '0') && (b <= '9')) return (byte)(b - '0');
+        if ((b >= 'a') && (b <= 'f')) return (byte)(b - 'a' + 10);
+        if ((b >= 'A') && (b <= 'F')) return (byte)(b - 'A' + 10);
+        return 0;
+    }
+
+}
diff --git a/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java
new file mode 100644
index 0000000..05e6cc6
--- /dev/null
+++ b/modules/tomcat-lite/java/org/apache/tomcat/lite/util/UrlUtils.java
@@ -0,0 +1,84 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.util;
+
+public class UrlUtils {
+
+    /** Used by webdav.
+     *
+     * Return a context-relative path, beginning with a "/", that represents
+     * the canonical version of the specified path after ".." and "." elements
+     * are resolved out.  If the specified path attempts to go outside the
+     * boundaries of the current context (i.e. too many ".." path elements
+     * are present), return <code>null</code> instead.
+     *
+     * @param path Path to be normalized
+     */
+    public static String normalize(String path) {
+
+        if (path == null)
+            return null;
+
+        // Create a place for the normalized path
+        String normalized = path;
+
+        if (normalized.equals("/."))
+            return "/";
+
+        // Normalize the slashes and add leading slash if necessary
+        if (normalized.indexOf('\\') >= 0)
+            normalized = normalized.replace('\\', '/');
+
+        if (!normalized.startsWith("/"))
+            normalized = "/" + normalized;
+
+        // Resolve occurrences of "//" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("//");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 1);
+        }
+
+        // Resolve occurrences of "/./" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/./");
+            if (index < 0)
+                break;
+            normalized = normalized.substring(0, index) +
+                normalized.substring(index + 2);
+        }
+
+        // Resolve occurrences of "/../" in the normalized path
+        while (true) {
+            int index = normalized.indexOf("/../");
+            if (index < 0)
+                break;
+            if (index == 0)
+                return (null);  // Trying to go outside our context
+            int index2 = normalized.lastIndexOf('/', index - 1);
+            normalized = normalized.substring(0, index2) +
+                normalized.substring(index + 3);
+        }
+
+        // Return the normalized path that we have completed
+        return (normalized);
+    }
+
+}
diff --git a/modules/tomcat-lite/pom.xml b/modules/tomcat-lite/pom.xml
new file mode 100644
index 0000000..742ae8d
--- /dev/null
+++ b/modules/tomcat-lite/pom.xml
@@ -0,0 +1,118 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+  <modelVersion>4.0.0</modelVersion>
+
+  <groupId>org.apache.tomcat.lite</groupId>
+  <artifactId>lite</artifactId>
+
+  <version>0.0.1-SNAPSHOT</version>
+
+  <dependencies>
+        <dependency>
+                <groupId>com.jcraft</groupId>
+                <artifactId>jzlib</artifactId>
+                <version>1.0.7</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>javax.servlet</groupId>
+                <artifactId>servlet-api</artifactId>
+                <version>2.5</version>
+        </dependency>
+        <dependency>
+                <groupId>junit</groupId>
+                <artifactId>junit</artifactId>
+                <version>3.8.2</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>commons-codec</groupId>
+                <artifactId>commons-codec</artifactId>
+                <version>1.4</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>org.apache.tomcat</groupId>
+                <artifactId>jasper</artifactId>
+                <version>6.0.20</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>org.apache.tomcat</groupId>
+                <artifactId>jasper-jdt</artifactId>
+                <version>6.0.20</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>org.apache.tomcat</groupId>
+                <artifactId>jasper-el</artifactId>
+                <version>6.0.20</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>asm</groupId>
+                <artifactId>asm</artifactId>
+                <version>3.1</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+                <groupId>asm</groupId>
+                <artifactId>asm-tree</artifactId>
+                <version>3.1</version>
+                <type>jar</type>
+                <scope>compile</scope>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.tomcat</groupId>
+        	<artifactId>coyote</artifactId>
+        	<version>6.0.20</version>
+        </dependency>
+        <dependency>
+        	<groupId>org.apache.ant</groupId>
+        	<artifactId>ant</artifactId>
+        	<version>1.7.1</version>
+        </dependency>
+  </dependencies>
+
+   <build>
+
+    <sourceDirectory>java</sourceDirectory>
+
+    <testSourceDirectory>test</testSourceDirectory>
+
+    <plugins>
+      <plugin>
+        <groupId>org.apache.maven.plugins</groupId>
+        <artifactId>maven-compiler-plugin</artifactId>
+        <configuration>
+          <source>1.6</source>
+          <target>1.6</target>
+          <excludes>
+            <exclude>org/apache/coyote/servlet/**</exclude>
+            <exclude>**/ServletApi30.java</exclude>
+          </excludes>
+          <testExcludes>
+            <exclude>org/apache/coyote/servlet/**</exclude>
+            <exclude>**/ServletApi30.java</exclude>
+          </testExcludes>
+        </configuration>
+      </plugin>
+    </plugins>
+
+    <testResources>
+      <testResource>
+        <directory>test</directory>
+        <excludes>
+          <exclude>**/*.java</exclude>
+        </excludes>
+       </testResource>
+   </testResources>
+
+  </build>
+</project>
diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java b/modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java
new file mode 100644
index 0000000..71b6bce
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/coyote/lite/ServletTests.java
@@ -0,0 +1,98 @@
+/*
+ */
+package org.apache.coyote.lite;
+
+import java.io.File;
+import java.io.IOException;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.tomcat.test.watchdog.WatchdogClient;
+
+import junit.framework.Test;
+
+/**
+ * Wrapper to run watchdog.
+ *
+ */
+public class ServletTests extends WatchdogClient {
+
+
+    public ServletTests() {
+        super();
+        goldenDir = getWatchdogdir() + "/src/clients/org/apache/jcheck/servlet/client/";
+        testMatch =
+            //"HttpServletResponseWrapperSetStatusMsgTest";
+            //"ServletContextAttributeAddedEventTest";
+            null;
+            // ex: "ServletToJSP";
+        file = getWatchdogdir() + "/src/conf/servlet-gtest.xml";
+        targetMatch = "gtestservlet-test";
+
+        port = 8883;
+        exclude = new String[] {
+                "DoInit1Test", // tomcat returns 404 if perm. unavailable
+                "HttpServletDoInit1Test",
+                "GetMajorVersionTest", // tomcat7
+                "GetMinorVersionTest",
+                "ServletToJSPErrorPageTest",
+                "ServletToJSPError502PageTest",
+        };
+    }
+
+    public ServletTests(String name) {
+       this();
+       super.single = name;
+       port = 8883;
+    }
+
+    protected void beforeSuite() {
+        // required for the tests
+        System.setProperty("org.apache.coyote.USE_CUSTOM_STATUS_MSG_IN_HEADER",
+                "true");
+
+        try {
+            initServerWithWatchdog(getWatchdogdir());
+        } catch (ServletException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (IOException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+    }
+
+    public void initServerWithWatchdog(String wdDir) throws ServletException,
+            IOException {
+        Tomcat tomcat = new Tomcat();
+        tomcat.setPort(port);
+
+        File f = new File(wdDir + "/build/webapps");
+        tomcat.setBaseDir(f.getAbsolutePath());
+
+        for (String s : new String[] {
+                "servlet-compat",
+                "servlet-tests",
+                "jsp-tests"} ) {
+            tomcat.addWebapp("/" + s, f.getCanonicalPath() + "/" + s);
+        }
+
+        TomcatStandaloneMain.setUp(tomcat, port);
+
+        try {
+            tomcat.start();
+        } catch (LifecycleException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        System.err.println("Init done");
+    }
+
+    /**
+     * Magic JUnit method
+     */
+    public static Test suite() {
+        return new ServletTests().getSuite();
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java b/modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java
new file mode 100644
index 0000000..ae72345
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/coyote/lite/Tomcat.java
@@ -0,0 +1,911 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.coyote.lite;
+
+import java.io.File;
+import java.io.IOException;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import javax.servlet.Servlet;
+import javax.servlet.ServletException;
+
+import org.apache.catalina.Container;
+import org.apache.catalina.Context;
+import org.apache.catalina.Lifecycle;
+import org.apache.catalina.LifecycleEvent;
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.LifecycleListener;
+import org.apache.catalina.Realm;
+import org.apache.catalina.connector.Connector;
+import org.apache.catalina.core.StandardContext;
+import org.apache.catalina.core.StandardEngine;
+import org.apache.catalina.core.StandardHost;
+import org.apache.catalina.core.StandardServer;
+import org.apache.catalina.core.StandardService;
+import org.apache.catalina.core.StandardWrapper;
+import org.apache.catalina.realm.RealmBase;
+import org.apache.catalina.session.StandardManager;
+import org.apache.catalina.startup.ContextConfig;
+
+// This class is here for compat with Tomcat6.
+
+// TODO: lazy init for the temp dir - only when a JSP is compiled or
+// get temp dir is called we need to create it. This will avoid the
+// need for the baseDir
+
+// TODO: allow contexts without a base dir - i.e.
+// only programmatic. This would disable the default servlet.
+
+/**
+ * Minimal tomcat starter for embedding/unit tests.
+ *
+ * Tomcat supports multiple styles of configuration and
+ * startup - the most common and stable is server.xml-based,
+ * implemented in org.apache.catalina.startup.Bootstrap.
+ *
+ * This class is for use in apps that embed tomcat.
+ * Requirements:
+ *
+ * - all tomcat classes and possibly servlets are in the classpath.
+ * ( for example all is in one big jar, or in eclipse CP, or in any other
+ * combination )
+ *
+ * - we need one temporary directory for work files
+ *
+ * - no config file is required. This class provides methods to
+ * use if you have a webapp with a web.xml file, but it is
+ * optional - you can use your own servlets.
+ *
+ * This class provides a main() and few simple CLI arguments,
+ * see setters for doc. It can be used for simple tests and
+ * demo.
+ *
+ * @see TestTomcat for examples on how to use this
+ * @author Costin Manolache
+ */
+public class Tomcat {
+    // Single engine, service, server, connector - few cases need more,
+    // they can use server.xml
+    protected StandardServer server;
+    protected StandardService service;
+    protected StandardEngine engine;
+    protected Connector connector; // for more - customize the classes
+
+    boolean started = false;
+    // To make it a bit easier to config for the common case
+    // ( one host, one context ).
+    protected StandardHost host;
+
+    // TODO: it's easy to add support for more hosts - but is it
+    // really needed ?
+
+    // TODO: allow use of in-memory connector
+
+    protected int port = 8080;
+    protected String hostname = "localhost";
+    protected String basedir;
+
+    // Default in-memory realm, will be set by default on
+    // created contexts. Can be replaced with setRealm() on
+    // the context.
+    protected Realm defaultRealm;
+    private Map<String, String> userPass = new HashMap<String, String>();
+    private Map<String, List<String>> userRoles =
+            new HashMap<String, List<String>>();
+    private Map<String, Principal> userPrincipals = new HashMap<String, Principal>();
+
+    public Tomcat() {
+        // NOOP
+    }
+
+    /**
+     * Tomcat needs a directory for temp files. This should be the
+     * first method called.
+     *
+     * By default, if this method is not called, we use:
+     *  - system properties - catalina.base, catalina.home
+     *  - $HOME/tomcat.$PORT
+     * ( /tmp doesn't seem a good choice for security ).
+     *
+     *
+     * TODO: better default ? Maybe current dir ?
+     * TODO: disable work dir if not needed ( no jsp, etc ).
+     */
+    public void setBaseDir(String basedir) {
+        this.basedir = basedir;
+    }
+
+    /**
+     * Set the port for the default connector. Must
+     * be called before start().
+     */
+    public void setPort(int port) {
+        this.port = port;
+    }
+
+    /**
+     * The the hostname of the default host, default is
+     * 'localhost'.
+     */
+    public void setHostname(String s) {
+        hostname = s;
+    }
+
+    /**
+     * Add a webapp using normal WEB-INF/web.xml if found.
+     *
+     * @param contextPath
+     * @param baseDir
+     * @return new StandardContext
+     * @throws ServletException
+     */
+    public StandardContext addWebapp(String contextPath,
+                                     String baseDir) throws ServletException {
+
+        return addWebapp(getHost(), contextPath, baseDir);
+    }
+
+
+    /**
+     * Add a context - programmatic mode, no web.xml used.
+     *
+     * API calls equivalent with web.xml:
+     *
+     * context-param
+     *  ctx.addParameter("name", "value");
+     *
+     *
+     * error-page
+     *    ErrorPage ep = new ErrorPage();
+     *    ep.setErrorCode(500);
+     *    ep.setLocation("/error.html");
+     *    ctx.addErrorPage(ep);
+     *
+     * ctx.addMimeMapping("ext", "type");
+     *
+     * Note: If you reload the Context, all your configuration will be lost. If
+     * you need reload support, consider using a LifecycleListener to provide
+     * your configuration.
+     *
+     * TODO: add the rest
+     *
+     *  @param contextPath "/" for root context.
+     *  @param baseDir base dir for the context, for static files. Must exist,
+     *  relative to the server home
+     */
+    public StandardContext addContext(String contextPath,
+                                      String baseDir) {
+        return addContext(getHost(), contextPath, baseDir);
+    }
+
+    /**
+     * Equivalent with
+     *  <servlet><servlet-name><servlet-class>.
+     *
+     * In general it is better/faster to use the method that takes a
+     * Servlet as param - this one can be used if the servlet is not
+     * commonly used, and want to avoid loading all deps.
+     * ( for example: jsp servlet )
+     *
+     * You can customize the returned servlet, ex:
+     *
+     *    wrapper.addInitParameter("name", "value");
+     *
+     * @param contextPath   Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servletClass  The class to be used for the Servlet
+     * @return The wrapper for the servlet
+     */
+    public StandardWrapper addServlet(String contextPath,
+            String servletName,
+            String servletClass) {
+        Container ctx = getHost().findChild(contextPath);
+        return addServlet((StandardContext) ctx,
+                servletName, servletClass);
+    }
+
+    /**
+     * Static version of {@link #addServlet(String, String, String)}
+     * @param ctx           Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servletClass  The class to be used for the Servlet
+     * @return The wrapper for the servlet
+     */
+    public static StandardWrapper addServlet(StandardContext ctx,
+                                      String servletName,
+                                      String servletClass) {
+        // will do class for name and set init params
+        StandardWrapper sw = (StandardWrapper)ctx.createWrapper();
+        sw.setServletClass(servletClass);
+        sw.setName(servletName);
+        ctx.addChild(sw);
+
+        return sw;
+    }
+
+    /**
+     * Add an existing Servlet to the context with no class.forName or
+     * initialisation.
+     * @param contextPath   Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servlet       The Servlet to add
+     * @return The wrapper for the servlet
+     */
+    public StandardWrapper addServlet(String contextPath,
+            String servletName,
+            Servlet servlet) {
+        Container ctx = getHost().findChild(contextPath);
+        return addServlet((StandardContext) ctx,
+                servletName, servlet);
+    }
+
+    /**
+     * Static version of {@link #addServlet(String, String, Servlet)}.
+     * @param ctx           Context to add Servlet to
+     * @param servletName   Servlet name (used in mappings)
+     * @param servlet       The Servlet to add
+     * @return The wrapper for the servlet
+     */
+    public static StandardWrapper addServlet(StandardContext ctx,
+                                      String servletName,
+                                      Servlet servlet) {
+        // will do class for name and set init params
+        StandardWrapper sw = new ExistingStandardWrapper(servlet);
+        sw.setName(servletName);
+        ctx.addChild(sw);
+
+        return sw;
+    }
+
+
+    /**
+     * Initialize and start the server.
+     * @throws LifecycleException
+     */
+    public void start() throws LifecycleException {
+        if (started) {
+            return;
+        }
+        started = true;
+        getServer();
+        getConnector();
+        server.start();
+    }
+
+    /**
+     * Stop the server.
+     * @throws LifecycleException
+     */
+    public void stop() throws LifecycleException {
+        getServer().stop();
+    }
+
+
+    /**
+     * Add a user for the in-memory realm. All created apps use this
+     * by default, can be replaced using setRealm().
+     *
+     */
+    public void addUser(String user, String pass) {
+        userPass.put(user, pass);
+    }
+
+    /**
+     * @see #addUser(String, String)
+     */
+    public void addRole(String user, String role) {
+        List<String> roles = userRoles.get(user);
+        if (roles == null) {
+            roles = new ArrayList<String>();
+            userRoles.put(user, roles);
+        }
+        roles.add(role);
+    }
+
+    // ------- Extra customization -------
+    // You can tune individual tomcat objects, using internal APIs
+
+    /**
+     * Get the default http connector. You can set more
+     * parameters - the port is already initialized.
+     *
+     * Alternatively, you can construct a Connector and set any params,
+     * then call addConnector(Connector)
+     *
+     * @return A connector object that can be customized
+     */
+    public Connector getConnector() {
+        getServer();
+        if (connector != null) {
+            return connector;
+        }
+        // This will load Apr connector if available,
+        // default to nio. I'm having strange problems with apr
+        // and for the use case the speed benefit wouldn't matter.
+
+        //connector = new Connector("HTTP/1.1");
+        try {
+            connector = new Connector("org.apache.coyote.http11.Http11Protocol");
+            connector.setPort(port);
+            service.addConnector( connector );
+        } catch (Exception e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return connector;
+    }
+
+    public void setConnector(Connector connector) {
+        this.connector = connector;
+    }
+
+    /**
+     * Get the service object. Can be used to add more
+     * connectors and few other global settings.
+     */
+    public StandardService getService() {
+        getServer();
+        return service;
+    }
+
+    /**
+     * Sets the current host - all future webapps will
+     * be added to this host. When tomcat starts, the
+     * host will be the default host.
+     *
+     * @param host
+     */
+    public void setHost(StandardHost host) {
+        this.host = host;
+    }
+
+    public StandardHost getHost() {
+        if (host == null) {
+            host = new StandardHost();
+            host.setName(hostname);
+
+            getEngine().addChild( host );
+        }
+        return host;
+    }
+
+    /**
+     * Set a custom realm for auth. If not called, a simple
+     * default will be used, using an internal map.
+     *
+     * Must be called before adding a context.
+     */
+    public void setDefaultRealm(Realm realm) {
+        defaultRealm = realm;
+    }
+
+
+    /**
+     * Access to the engine, for further customization.
+     */
+    public StandardEngine getEngine() {
+        if(engine == null ) {
+            getServer();
+            engine = new StandardEngine();
+            engine.setName( "Tomcat" );
+            engine.setDefaultHost(hostname);
+            service.setContainer(engine);
+        }
+        return engine;
+    }
+
+    /**
+     * Get the server object. You can add listeners and few more
+     * customizations. JNDI is disabled by default.
+     */
+    public StandardServer getServer() {
+
+        if (server != null) {
+            return server;
+        }
+
+        initBaseDir();
+
+        System.setProperty("catalina.useNaming", "false");
+
+        server = new StandardServer();
+        server.setPort( -1 );
+
+        service = new StandardService();
+        service.setName("Tomcat");
+        server.addService( service );
+        return server;
+    }
+
+    public StandardContext addContext(StandardHost host,
+                                      String contextPath,
+                                      String dir) {
+        silence(contextPath);
+        StandardContext ctx = new StandardContext();
+        ctx.setPath( contextPath );
+        ctx.setDocBase(dir);
+        ctx.addLifecycleListener(new FixContextListener());
+
+        if (host == null) {
+            getHost().addChild(ctx);
+        } else {
+            host.addChild(ctx);
+        }
+        return ctx;
+    }
+
+    public StandardContext addWebapp(StandardHost host,
+                                     String url, String path) {
+        silence(url);
+
+        StandardContext ctx = new StandardContext();
+        ctx.setPath( url );
+        ctx.setDocBase(path);
+        if (defaultRealm == null) {
+            initSimpleAuth();
+        }
+        ctx.setRealm(defaultRealm);
+        ctx.addLifecycleListener(new DefaultWebXmlListener());
+
+        ContextConfig ctxCfg = new ContextConfig();
+        ctx.addLifecycleListener( ctxCfg );
+        // prevent it from looking ( if it finds one - it'll have dup error )
+        ctxCfg.setDefaultWebXml("org/apache/catalin/startup/NO_DEFAULT_XML");
+
+        if (host == null) {
+            getHost().addChild(ctx);
+        } else {
+            host.addChild(ctx);
+        }
+
+        return ctx;
+    }
+
+
+    // ---------- Helper methods and classes -------------------
+
+    /**
+     * Initialize an in-memory realm. You can replace it
+     * for contexts with a real one.
+     */
+    protected void initSimpleAuth() {
+        defaultRealm = new RealmBase() {
+            @Override
+            protected String getName() {
+                return "Simple";
+            }
+
+            @Override
+            protected String getPassword(String username) {
+                return userPass.get(username);
+            }
+
+            @Override
+            protected Principal getPrincipal(final String username) {
+                Principal p = userPrincipals.get(username);
+                if (p == null) {
+                    String pass = userPass.get(username);
+                    if (pass != null) {
+                        p = new Principal() {
+
+                            @Override
+                            public String getName() {
+                                return username;
+                            }
+
+                        };
+                    }
+                }
+                return p;
+            }
+
+        };
+    }
+
+    protected void initBaseDir() {
+        if (basedir == null) {
+            basedir = System.getProperty("catalina.base");
+        }
+        if (basedir == null) {
+            basedir = System.getProperty("catalina.home");
+        }
+        if (basedir == null) {
+            // Create a temp dir.
+            basedir = System.getProperty("user.dir") +
+                "/tomcat." + port;
+            File home = new File(basedir);
+            home.mkdir();
+            if (!home.isAbsolute()) {
+                try {
+                    basedir = home.getCanonicalPath();
+                } catch (IOException e) {
+                    basedir = home.getAbsolutePath();
+                }
+            }
+        }
+        System.setProperty("catalina.home", basedir);
+        System.setProperty("catalina.base", basedir);
+    }
+
+    static String[] silences = new String[] {
+        "org.apache.coyote.http11.Http11Protocol",
+        "org.apache.catalina.core.StandardService",
+        "org.apache.catalina.core.StandardEngine",
+        "org.apache.catalina.startup.ContextConfig",
+        "org.apache.catalina.core.ApplicationContext",
+        "org.apache.catalina.core.AprLifecycleListener"
+    };
+
+    /**
+     * Controls if the loggers will be silenced or not.
+     * @param silent    <code>true</code> sets the log level to WARN for the
+     *                  loggers that log information on Tomcat start up. This
+     *                  prevents the usual startup information being logged.
+     *                  <code>false</code> sets the log level to the default
+     *                  level of INFO.
+     */
+    public void setSilent(boolean silent) {
+        for (String s : silences) {
+            if (silent) {
+                Logger.getLogger(s).setLevel(Level.WARNING);
+            } else {
+                Logger.getLogger(s).setLevel(Level.INFO);
+            }
+        }
+    }
+
+    private void silence(String ctx) {
+        String base = "org.apache.catalina.core.ContainerBase.[default].[";
+        base += getHost().getName();
+        base += "].[";
+        base += ctx;
+        base += "]";
+        Logger.getLogger(base).setLevel(Level.WARNING);
+    }
+
+    /**
+     * Enables JNDI naming which is disabled by default.
+     */
+    public void enableNaming() {
+        // Make sure getServer() has been called as that is where naming is
+        // disabled
+        getServer();
+
+        System.setProperty("catalina.useNaming", "true");
+        String value = "org.apache.naming";
+        String oldValue =
+            System.getProperty(javax.naming.Context.URL_PKG_PREFIXES);
+        if (oldValue != null) {
+            value = value + ":" + oldValue;
+        }
+        System.setProperty(javax.naming.Context.URL_PKG_PREFIXES, value);
+        value = System.getProperty
+            (javax.naming.Context.INITIAL_CONTEXT_FACTORY);
+        if (value == null) {
+            System.setProperty
+                (javax.naming.Context.INITIAL_CONTEXT_FACTORY,
+                 "org.apache.naming.java.javaURLContextFactory");
+        }
+    }
+
+    /**
+     * Provide default configuration for a context. This is the programmatic
+     * equivalent of the default web.xml.
+     *
+     *  TODO: in normal tomcat, if default-web.xml is not found, use this
+     *  method
+     *
+     * @param contextPath   The context to set the defaults for
+     */
+    public void initWebappDefaults(String contextPath) {
+        Container ctx = getHost().findChild(contextPath);
+        initWebappDefaults((StandardContext) ctx);
+    }
+
+    /**
+     * Static version of {@link #initWebappDefaults(String)}
+     * @param ctx   The context to set the defaults for
+     */
+    public static void initWebappDefaults(StandardContext ctx) {
+        // Default servlet
+        StandardWrapper servlet = addServlet(
+                ctx, "default", "org.apache.catalina.servlets.DefaultServlet");
+        servlet.setLoadOnStartup(1);
+
+        // JSP servlet (by class name - to avoid loading all deps)
+        servlet = addServlet(
+                ctx, "jsp", "org.apache.jasper.servlet.JspServlet");
+        servlet.addInitParameter("fork", "false");
+        servlet.setLoadOnStartup(3);
+
+        // Servlet mappings
+        ctx.addServletMapping("/", "default");
+        ctx.addServletMapping("*.jsp", "jsp");
+        ctx.addServletMapping("*.jspx", "jsp");
+
+        // Sessions
+        ctx.setManager( new StandardManager());
+        ctx.setSessionTimeout(30);
+
+        // MIME mappings
+        for (int i = 0; i < DEFAULT_MIME_MAPPINGS.length; ) {
+            ctx.addMimeMapping(DEFAULT_MIME_MAPPINGS[i++],
+                    DEFAULT_MIME_MAPPINGS[i++]);
+        }
+
+        // Welcome files
+        ctx.addWelcomeFile("index.html");
+        ctx.addWelcomeFile("index.htm");
+        ctx.addWelcomeFile("index.jsp");
+    }
+
+
+    /**
+     * Fix startup sequence - required if you don't use web.xml.
+     *
+     * The start() method in context will set 'configured' to false - and
+     * expects a listener to set it back to true.
+     */
+    public static class FixContextListener implements LifecycleListener {
+
+        public void lifecycleEvent(LifecycleEvent event) {
+            try {
+                Context context = (Context) event.getLifecycle();
+                if (event.getType().equals(Lifecycle.START_EVENT)) {
+                    context.setConfigured(true);
+                }
+            } catch (ClassCastException e) {
+                return;
+            }
+        }
+
+    }
+
+
+    /**
+     * Fix reload - required if reloading and using programmatic configuration.
+     * When a context is reloaded, any programmatic configuration is lost. This
+     * listener sets the equivalent of conf/web.xml when the context starts. The
+     * context needs to be an instance of StandardContext for this listener to
+     * have any effect.
+     */
+    public static class DefaultWebXmlListener implements LifecycleListener {
+        public void lifecycleEvent(LifecycleEvent event) {
+            if (Lifecycle.BEFORE_START_EVENT.equals(event.getType()) &&
+                    event.getLifecycle() instanceof StandardContext) {
+                initWebappDefaults((StandardContext) event.getLifecycle());
+            }
+        }
+    }
+
+
+    /**
+     * Helper class for wrapping existing servlets. This disables servlet
+     * lifecycle and normal reloading, but also reduces overhead and provide
+     * more direct control over the servlet.
+     */
+    public static class ExistingStandardWrapper extends StandardWrapper {
+        private Servlet existing;
+        boolean init = false;
+
+        public ExistingStandardWrapper( Servlet existing ) {
+            this.existing = existing;
+        }
+        @Override
+        public synchronized Servlet loadServlet() throws ServletException {
+            if (!init) {
+                existing.init(facade);
+                init = true;
+            }
+            return existing;
+
+        }
+        @Override
+        public long getAvailable() {
+            return 0;
+        }
+        @Override
+        public boolean isUnavailable() {
+            return false;
+        }
+    }
+
+    /**
+     * TODO: would a properties resource be better ? Or just parsing
+     * /etc/mime.types ?
+     * This is needed because we don't use the default web.xml, where this
+     * is encoded.
+     */
+    public static final String[] DEFAULT_MIME_MAPPINGS = {
+        "abs", "audio/x-mpeg",
+        "ai", "application/postscript",
+        "aif", "audio/x-aiff",
+        "aifc", "audio/x-aiff",
+        "aiff", "audio/x-aiff",
+        "aim", "application/x-aim",
+        "art", "image/x-jg",
+        "asf", "video/x-ms-asf",
+        "asx", "video/x-ms-asf",
+        "au", "audio/basic",
+        "avi", "video/x-msvideo",
+        "avx", "video/x-rad-screenplay",
+        "bcpio", "application/x-bcpio",
+        "bin", "application/octet-stream",
+        "bmp", "image/bmp",
+        "body", "text/html",
+        "cdf", "application/x-cdf",
+        "cer", "application/pkix-cert",
+        "class", "application/java",
+        "cpio", "application/x-cpio",
+        "csh", "application/x-csh",
+        "css", "text/css",
+        "dib", "image/bmp",
+        "doc", "application/msword",
+        "dtd", "application/xml-dtd",
+        "dv", "video/x-dv",
+        "dvi", "application/x-dvi",
+        "eps", "application/postscript",
+        "etx", "text/x-setext",
+        "exe", "application/octet-stream",
+        "gif", "image/gif",
+        "gtar", "application/x-gtar",
+        "gz", "application/x-gzip",
+        "hdf", "application/x-hdf",
+        "hqx", "application/mac-binhex40",
+        "htc", "text/x-component",
+        "htm", "text/html",
+        "html", "text/html",
+        "ief", "image/ief",
+        "jad", "text/vnd.sun.j2me.app-descriptor",
+        "jar", "application/java-archive",
+        "java", "text/x-java-source",
+        "jnlp", "application/x-java-jnlp-file",
+        "jpe", "image/jpeg",
+        "jpeg", "image/jpeg",
+        "jpg", "image/jpeg",
+        "js", "application/javascript",
+        "jsf", "text/plain",
+        "jspf", "text/plain",
+        "kar", "audio/midi",
+        "latex", "application/x-latex",
+        "m3u", "audio/x-mpegurl",
+        "mac", "image/x-macpaint",
+        "man", "text/troff",
+        "mathml", "application/mathml+xml",
+        "me", "text/troff",
+        "mid", "audio/midi",
+        "midi", "audio/midi",
+        "mif", "application/x-mif",
+        "mov", "video/quicktime",
+        "movie", "video/x-sgi-movie",
+        "mp1", "audio/mpeg",
+        "mp2", "audio/mpeg",
+        "mp3", "audio/mpeg",
+        "mp4", "video/mp4",
+        "mpa", "audio/mpeg",
+        "mpe", "video/mpeg",
+        "mpeg", "video/mpeg",
+        "mpega", "audio/x-mpeg",
+        "mpg", "video/mpeg",
+        "mpv2", "video/mpeg2",
+        "nc", "application/x-netcdf",
+        "oda", "application/oda",
+        "odb", "application/vnd.oasis.opendocument.database",
+        "odc", "application/vnd.oasis.opendocument.chart",
+        "odf", "application/vnd.oasis.opendocument.formula",
+        "odg", "application/vnd.oasis.opendocument.graphics",
+        "odi", "application/vnd.oasis.opendocument.image",
+        "odm", "application/vnd.oasis.opendocument.text-master",
+        "odp", "application/vnd.oasis.opendocument.presentation",
+        "ods", "application/vnd.oasis.opendocument.spreadsheet",
+        "odt", "application/vnd.oasis.opendocument.text",
+        "otg", "application/vnd.oasis.opendocument.graphics-template",
+        "oth", "application/vnd.oasis.opendocument.text-web",
+        "otp", "application/vnd.oasis.opendocument.presentation-template",
+        "ots", "application/vnd.oasis.opendocument.spreadsheet-template ",
+        "ott", "application/vnd.oasis.opendocument.text-template",
+        "ogx", "application/ogg",
+        "ogv", "video/ogg",
+        "oga", "audio/ogg",
+        "ogg", "audio/ogg",
+        "spx", "audio/ogg",
+        "flac", "audio/flac",
+        "anx", "application/annodex",
+        "axa", "audio/annodex",
+        "axv", "video/annodex",
+        "xspf", "application/xspf+xml",
+        "pbm", "image/x-portable-bitmap",
+        "pct", "image/pict",
+        "pdf", "application/pdf",
+        "pgm", "image/x-portable-graymap",
+        "pic", "image/pict",
+        "pict", "image/pict",
+        "pls", "audio/x-scpls",
+        "png", "image/png",
+        "pnm", "image/x-portable-anymap",
+        "pnt", "image/x-macpaint",
+        "ppm", "image/x-portable-pixmap",
+        "ppt", "application/vnd.ms-powerpoint",
+        "pps", "application/vnd.ms-powerpoint",
+        "ps", "application/postscript",
+        "psd", "image/vnd.adobe.photoshop",
+        "qt", "video/quicktime",
+        "qti", "image/x-quicktime",
+        "qtif", "image/x-quicktime",
+        "ras", "image/x-cmu-raster",
+        "rdf", "application/rdf+xml",
+        "rgb", "image/x-rgb",
+        "rm", "application/vnd.rn-realmedia",
+        "roff", "text/troff",
+        "rtf", "application/rtf",
+        "rtx", "text/richtext",
+        "sh", "application/x-sh",
+        "shar", "application/x-shar",
+        /*"shtml", "text/x-server-parsed-html",*/
+        "sit", "application/x-stuffit",
+        "snd", "audio/basic",
+        "src", "application/x-wais-source",
+        "sv4cpio", "application/x-sv4cpio",
+        "sv4crc", "application/x-sv4crc",
+        "svg", "image/svg+xml",
+        "svgz", "image/svg+xml",
+        "swf", "application/x-shockwave-flash",
+        "t", "text/troff",
+        "tar", "application/x-tar",
+        "tcl", "application/x-tcl",
+        "tex", "application/x-tex",
+        "texi", "application/x-texinfo",
+        "texinfo", "application/x-texinfo",
+        "tif", "image/tiff",
+        "tiff", "image/tiff",
+        "tr", "text/troff",
+        "tsv", "text/tab-separated-values",
+        "txt", "text/plain",
+        "ulw", "audio/basic",
+        "ustar", "application/x-ustar",
+        "vxml", "application/voicexml+xml",
+        "xbm", "image/x-xbitmap",
+        "xht", "application/xhtml+xml",
+        "xhtml", "application/xhtml+xml",
+        "xls", "application/vnd.ms-excel",
+        "xml", "application/xml",
+        "xpm", "image/x-xpixmap",
+        "xsl", "application/xml",
+        "xslt", "application/xslt+xml",
+        "xul", "application/vnd.mozilla.xul+xml",
+        "xwd", "image/x-xwindowdump",
+        "vsd", "application/vnd.visio",
+        "wav", "audio/x-wav",
+        "wbmp", "image/vnd.wap.wbmp",
+        "wml", "text/vnd.wap.wml",
+        "wmlc", "application/vnd.wap.wmlc",
+        "wmls", "text/vnd.wap.wmlsc",
+        "wmlscriptc", "application/vnd.wap.wmlscriptc",
+        "wmv", "video/x-ms-wmv",
+        "wrl", "model/vrml",
+        "wspolicy", "application/wspolicy+xml",
+        "Z", "application/x-compress",
+        "z", "application/x-compress",
+        "zip", "application/zip"
+    };
+}
diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java
new file mode 100644
index 0000000..a995076
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatLiteCoyoteTest.java
@@ -0,0 +1,42 @@
+/*
+ */
+package org.apache.coyote.lite;
+
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.http.HttpClient;
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.io.BBuffer;
+
+/**
+ * Very simple test.
+ */
+public class TomcatLiteCoyoteTest extends TestCase {
+    static int port = 8884;
+    static {
+        Logger.getLogger("org.apache.catalina.core.StandardService").setLevel(Level.WARNING);
+        Logger.getLogger("org.apache.catalina.core.StandardEngine").setLevel(Level.WARNING);
+        Logger.getLogger("org.apache.catalina.startup.ContextConfig").setLevel(Level.WARNING);
+    }
+    static Tomcat main = TomcatStandaloneMain.setUp(port);
+
+    public void testSimple() throws IOException {
+        HttpConnector clientCon = HttpClient.newClient();
+
+        HttpChannel ch = clientCon.get("localhost", port);
+        ch.getRequest().setRequestURI("/index.html");
+        ch.getRequest().send();
+
+        BBuffer res = ch.readAll(null, 0);
+
+        assertTrue(res.toString(),
+                res.toString().indexOf("<title>Apache Tomcat</title>") >= 0);
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatStandaloneMain.java b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatStandaloneMain.java
new file mode 100644
index 0000000..98cf16e
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/coyote/lite/TomcatStandaloneMain.java
@@ -0,0 +1,59 @@
+/*
+ */
+package org.apache.coyote.lite;
+
+import javax.servlet.ServletException;
+
+import org.apache.catalina.LifecycleException;
+import org.apache.catalina.connector.Connector;
+import org.apache.tomcat.lite.TestMain;
+
+/**
+ * Startup tomcat + coyote lite connector.
+ * No config files used.
+ *
+ * @author Costin Manolache
+ */
+public class TomcatStandaloneMain {
+
+    public static Tomcat setUp(int port) {
+        try {
+            Tomcat tomcat = new Tomcat();
+
+            tomcat.setPort(port);
+            String base = TestMain.findDir("/output/build");
+            tomcat.setBaseDir(base);
+            // Absolute path - tomcat6 and 7 are different,
+            // 7 adds webapps.
+            tomcat.addWebapp("/", base + "/webapps/ROOT");
+
+            LiteProtocolHandler litePH = setUp(tomcat, port);
+
+            tomcat.start();
+            return tomcat;
+        } catch (LifecycleException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        } catch (ServletException e) {
+            // TODO Auto-generated catch block
+            e.printStackTrace();
+        }
+        return null;
+    }
+
+    public static LiteProtocolHandler setUp(Tomcat tomcat, int port) {
+        Connector connector;
+        try {
+            connector = new Connector(LiteProtocolHandler.class.getName());
+            tomcat.getService().addConnector(connector);
+            connector.setPort(port);
+            tomcat.setConnector(connector);
+            LiteProtocolHandler ph =
+                (LiteProtocolHandler) connector.getProtocolHandler();
+            return ph;
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java
new file mode 100644
index 0000000..d81aa7c
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/TestMain.java
@@ -0,0 +1,313 @@
+/*
+ */
+package org.apache.tomcat.lite;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.reflect.Constructor;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.BaseMapper;
+import org.apache.tomcat.lite.http.HttpClient;
+import org.apache.tomcat.lite.http.Dispatcher;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpServer;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.services.EchoCallback;
+import org.apache.tomcat.lite.http.services.SleepCallback;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.jsse.JsseSslProvider;
+import org.apache.tomcat.lite.proxy.HttpProxyService;
+import org.apache.tomcat.lite.proxy.StaticContentService;
+import org.apache.tomcat.lite.service.IOStatus;
+
+/**
+ * Laucher for tomcat-lite standalone, configured with test handlers.
+ *
+ * Used in tests - one is running for the entire suite.
+ *
+ * @author Costin Manolache
+ */
+public class TestMain {
+
+    static {
+        JsseSslProvider.testModeURLConnection();
+    }
+
+    static TestMain defaultServer;
+
+    private boolean init = false;
+
+    HttpConnector testClient;
+    HttpConnector testServer;
+    HttpConnector testProxy;
+    HttpConnector sslServer;
+    HttpProxyService proxy;
+
+    public TestMain() {
+        init();
+    }
+
+    protected void init() {
+        testClient = HttpClient.newClient();
+    }
+
+    /**
+     * A single instance used for all tests.
+     */
+    public static TestMain shared() {
+        if (defaultServer == null) {
+            defaultServer = new TestMain();
+            defaultServer.run();
+        }
+        return defaultServer;
+    }
+
+    public static HttpConnector getTestServer() {
+        return shared().testServer;
+    }
+
+    public HttpConnector getClient() {
+        return shared().testClient;
+    }
+
+    public static BaseMapper.Context initTestContext(Dispatcher d) throws IOException {
+        BaseMapper.Context mCtx = d.addContext(null, "", null, null, null, null);
+
+        mCtx.addWrapper("/", new StaticContentService()
+            .setContentType("text/html")
+            .setData("<a href='/proc/cpool/client'>Client pool</a><br>" +
+                    "<a href='/proc/cpool/server'>Server pool</a><br>" +
+                    "<a href='/proc/cpool/proxy'>Proxy pool</a><br>" +
+                    ""));
+
+        mCtx.addWrapper("/favicon.ico",
+                new StaticContentService().setStatus(404).setData("Not found"));
+
+        mCtx.addWrapper("/hello", new StaticContentService().setData("Hello world"));
+        mCtx.addWrapper("/2nd", new StaticContentService().setData("Hello world2"));
+        mCtx.addWrapper("/echo/*", new EchoCallback());
+
+        mCtx.addWrapper("/sleep/1", new SleepCallback().setData("sleep 1"));
+        mCtx.addWrapper("/sleep/10", new SleepCallback().sleep(10000).setData(
+                "sleep 1"));
+
+        mCtx.addWrapper("/chunked/*", new StaticContentService().setData("AAAA")
+                .chunked());
+        mCtx.addWrapper("/helloClose", new HttpService() {
+            @Override
+            public void service(HttpRequest httpReq, HttpResponse httpRes)
+                    throws IOException {
+                httpRes.setHeader("Connection", "close");
+                httpRes.getBodyWriter().write("Hello");
+            }
+        });
+        return mCtx;
+    }
+
+    public void initTestCallback(Dispatcher d) throws IOException {
+        BaseMapper.Context mCtx = initTestContext(d);
+        mCtx.addWrapper("/proc/cpool/client", new IOStatus(testClient.cpool));
+        mCtx.addWrapper("/proc/cpool/proxy", new IOStatus(testProxy.cpool));
+        mCtx.addWrapper("/proc/cpool/server", new IOStatus(testServer.cpool));
+    }
+
+    public void run() {
+        try {
+            startAll();
+            // TODO(costin): clean up
+            // Hook in JMX and debug properties
+            try {
+                Class c = Class.forName("org.apache.tomcat.lite.TomcatLiteJmx");
+                Constructor constructor = c.getConstructor(TestMain.class);
+                constructor.newInstance(this);
+            } catch (Throwable t) {
+                // ignore
+            }
+        } catch (Throwable t) {
+            t.printStackTrace();
+        }
+    }
+
+    public static String findDir(String dir) {
+        String path = ".";
+        for (int i = 0; i < 5; i++) {
+            File f = new File(path + dir);
+            if (f.exists()) {
+                try {
+                    return f.getCanonicalPath();
+                } catch (IOException e) {
+                    return f.getAbsolutePath();
+                }
+            }
+            path = path + "/..";
+        }
+        return null;
+    }
+
+    public int getServerPort() {
+        return 8802;
+    }
+
+    public int getProxyPort() {
+        return 8903;
+    }
+
+    public int getSslServerPort() {
+        return 8443;
+    }
+
+    static String PRIVATE_KEY =
+    "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBALsz2milZGHliWte61TfMTSwpAdq" +
+"9uJkMTqgpSVtwxxOe8kT84QtIzhdAsQYjRz9ZtQn9DYWhJQs/cs/R3wWsjWwgiFHLzGalvsmMYJ3" +
+"vBO8VMj762fAWu7GjUApIXcxMJoK4sQUpZKbqTuXpwzVUeeqBcspsIDgOLCo233G7/fBAgMBAAEC" +
+"gYAWEaDX4VeaKuMuYzw+/yjf20sbDMMaIVGkZbfSV8Q+nAn/xHhaxq92P5DJ6VMJbd4neKZTkggD" +
+"J+KriUQ2Hr7XXd/nM+sllaDWGmUnMYFI4txaNkikMA3ZyE/Xa79eDpTnSst8Nm11vrX9oF/hDNo4" +
+"dhbU1krjAwVl/WijzSk4gQJBANvSmsmdjPlzvGNE11Aq3Ffb9/SqAOdE8NevMFeVKtBEKHIe1WlO" +
+"ThRyWv3I8bUKTQMNULruSFVghTh6Hkt/CBkCQQDaAuxaXjv2voYozkOviXMpt0X5LZJMQu2gFc2x" +
+"6UgBqYP2pNGDdRVWpbxF65PpXcLNKllCss2WB8i8kdeixYHpAkEAnIrzfia7sR2RiCQLLWUIe20D" +
+"vHGgqRG4bfCtfYGV9rDDGNoKYq7H/dmeIOML9kA6rbS6zBRK4LoWxSx6DIuPaQJAL2c3USbwTuR6" +
+"c2D2IrL2UXnCQz3/c4mR9Z8IDMk2mPXs9bI8xCKvMxnyaBmjHbj/ZHDy26fZP+gNY8MqagAcEQJA" +
+"SidPwFV6cO8LCIA43wSVHlKZt4yU5wa9EWfzqVZxj7VSav7431kuxktW/YlwwxO4Pn8hgpPqD+W1" +
+"E+Ssocxi8A==";
+		
+    static String CERTIFICATE = "-----BEGIN CERTIFICATE-----\n" +
+"MIIC5DCCAk2gAwIBAgIJAMa8ioWQMpEZMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV" +
+"BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEChMJbG9jYWxob3N0MRIwEAYDVQQL" +
+"Ewlsb2NhbGhvc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDAyMjYyMzIxNDBa" +
+"Fw0xMTAyMjYyMzIxNDBaMFYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAG" +
+"A1UEChMJbG9jYWxob3N0MRIwEAYDVQQLEwlsb2NhbGhvc3QxEjAQBgNVBAMTCWxv" +
+"Y2FsaG9zdDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAuzPaaKVkYeWJa17r" +
+"VN8xNLCkB2r24mQxOqClJW3DHE57yRPzhC0jOF0CxBiNHP1m1Cf0NhaElCz9yz9H" +
+"fBayNbCCIUcvMZqW+yYxgne8E7xUyPvrZ8Ba7saNQCkhdzEwmgrixBSlkpupO5en" +
+"DNVR56oFyymwgOA4sKjbfcbv98ECAwEAAaOBuTCBtjAdBgNVHQ4EFgQUj3OnBK8R" +
+"UN2CcmPvfQ1/IBeFwn8wgYYGA1UdIwR/MH2AFI9zpwSvEVDdgnJj730NfyAXhcJ/" +
+"oVqkWDBWMQswCQYDVQQGEwJVUzELMAkGA1UECBMCQ0ExEjAQBgNVBAoTCWxvY2Fs" +
+"aG9zdDESMBAGA1UECxMJbG9jYWxob3N0MRIwEAYDVQQDEwlsb2NhbGhvc3SCCQDG" +
+"vIqFkDKRGTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4GBAKcJWWZbHRuG" +
+"77ir1ETltxNIsAFvuhDD6E68eBwpviWfKhFxiOdD1vmAGqWWDYpmgORBGxFMZxTq" +
+"c82iSbM0LseFeHwxAfeNXosSShMFtQzKt2wKZLLQB/Oqrea32m4hU//NP8rNbTux" +
+"dcAHeNQEDB5EUUSewAlh+fUE6HB6c8j0\n" +
+"-----END CERTIFICATE-----\n\n";
+
+    protected synchronized void startAll() throws IOException {
+        if (init) {
+            System.err.println("2x init ???");
+        } else {
+            init = true;
+            boolean debug = false;
+            if (debug) {
+                System.setProperty("javax.net.debug", "ssl");
+                System.setProperty("jsse", "conn_state,alert,engine,record,ssocket,socket,prf");
+                Logger.getLogger("SSL").setLevel(Level.FINEST);
+                testClient.setDebug(true);
+                testClient.setDebugHttp(true);
+            }
+
+            proxy = new HttpProxyService()
+                .withHttpClient(testClient);
+            testProxy = HttpServer.newServer(getProxyPort());
+
+            if (debug) {
+                testProxy.setDebugHttp(true);
+                testProxy.setDebug(true);
+            }
+
+            // dispatcher rejects 'http://'
+            testProxy.setHttpService(proxy);
+            try {
+                testProxy.start();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+
+            testServer = HttpServer.newServer(getServerPort());
+            if (debug) {
+                testServer.setDebugHttp(true);
+                testServer.setDebug(true);
+            }
+            initTestCallback(testServer.getDispatcher());
+            try {
+                testServer.start();
+            } catch (IOException e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+
+//            Base64 b64 = new Base64();
+//            byte[] keyBytes = b64.decode(PRIVATE_KEY);
+
+            sslServer = HttpServer.newSslServer(getSslServerPort());
+
+            if (debug) {
+                sslServer.setDebug(true);
+                sslServer.setDebugHttp(true);
+            }
+            JsseSslProvider sslCon = (JsseSslProvider) sslServer.getSslProvider();
+
+            sslCon = sslCon
+                .setKeyRes("org/apache/tomcat/lite/http/genrsa_512.cert",
+                        "org/apache/tomcat/lite/http/genrsa_512.der");
+            initTestCallback(sslServer.getDispatcher());
+            sslServer.start();
+        }
+
+        Runtime.getRuntime().addShutdownHook(new Thread() {
+            public void run() {
+                System.err.println("Done");
+            }
+            public void start() {
+                System.err.println("Done1");
+            }
+        });
+    }
+
+    /**
+     * Blocking get, returns when the body has been read.
+     */
+    public static BBuffer get(String url) throws IOException {
+
+        BBuffer out = BBuffer.allocate();
+
+        HttpRequest aclient = HttpClient.newClient().request(url);
+        aclient.send();
+        aclient.readAll(out,
+                //Long.MAX_VALUE);//
+                2000000);
+        aclient.release(); // return connection to pool
+        return out;
+    }
+
+    public static BBuffer getUrl(String path) throws IOException {
+        BBuffer out = BBuffer.allocate();
+        getUrl(path, out);
+        return out;
+    }
+
+    public static HttpURLConnection getUrl(String path,
+                             BBuffer out) throws IOException {
+        URL url = new URL(path);
+        HttpURLConnection connection =
+            (HttpURLConnection) url.openConnection();
+        connection.setReadTimeout(10000);
+        connection.connect();
+        int rc = connection.getResponseCode();
+        InputStream is = connection.getInputStream();
+        BufferedInputStream bis = new BufferedInputStream(is);
+        byte[] buf = new byte[2048];
+        int rd = 0;
+        while((rd = bis.read(buf)) > 0) {
+            out.append(buf, 0, rd);
+        }
+        return connection;
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/ClientTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/ClientTest.java
new file mode 100644
index 0000000..226cad3
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/ClientTest.java
@@ -0,0 +1,60 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+import org.apache.tomcat.lite.TestMain;
+
+import junit.framework.TestCase;
+
+/**
+ * Examples and tests for Tomcat-lite in client mode.
+ *
+ */
+public class ClientTest extends TestCase {
+
+    /**
+     * All connectors created this way will share a single
+     * IO thread. Each connector will have its keep-alive
+     * pool - so it's better to share them.
+     *
+     * Since I want to test keep-alive works, I use a static one
+     */
+    static HttpConnector httpCon = HttpClient.newClient();
+
+    /**
+     * Start a http server, runs on 8802 - shared by all tests.
+     * Will use /echo handler.
+     */
+    static HttpConnector testServer = TestMain.getTestServer();
+
+
+    public void testSimpleBlocking() throws IOException {
+        HttpRequest req = httpCon.request("http://localhost:8802/echo/test1");
+        HttpResponse res = req.waitResponse();
+
+        assertEquals(200, res.getStatus());
+        //assertEquals("", res.getHeader(""));
+
+        BufferedReader reader = res.getReader();
+        String line1 = reader.readLine();
+        assertEquals("REQ HEAD:", line1);
+    }
+
+    public void testSimpleCallback() throws IOException {
+
+    }
+
+    public void testGetParams() throws IOException {
+    }
+
+    public void testPostParams() throws IOException {
+    }
+
+    public void testPostBody() throws IOException {
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/CompressFilterTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/CompressFilterTest.java
new file mode 100644
index 0000000..0ec3b82
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/CompressFilterTest.java
@@ -0,0 +1,82 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.util.Random;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.io.IOBuffer;
+
+public class CompressFilterTest extends TestCase {
+
+    CompressFilter cf = new CompressFilter();
+
+    private void check(String clear, String xtra) throws Exception {
+        IOBuffer in = new IOBuffer();
+        IOBuffer out = new IOBuffer();
+
+        in.append(clear);
+        in.close();
+
+        cf.compress(in, out);
+
+//        BBuffer bb = out.copyAll(null);
+//        String hd = Hex.getHexDump(bb.array(), bb.position(),
+//                bb.remaining(), true);
+//        System.err.println(hd);
+
+        if (xtra != null) {
+            out.append(xtra);
+        }
+        in.recycle();
+        out.close();
+        cf.decompress(out, in);
+
+        assertEquals(in.copyAll(null).toString(), clear);
+        assertTrue(in.isAppendClosed());
+
+        if (xtra != null) {
+            assertEquals(out.copyAll(null).toString(), xtra);
+        }
+    }
+
+    public void test1() throws Exception {
+        check("X1Y2Z3", null);
+    }
+
+    public void testXtra() throws Exception {
+        check("X1Y2Z3", "GET /");
+    }
+
+    public void testLarge() throws Exception {
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < 2 * 1024; i++) {
+            sb.append("0123456789012345");
+        }
+        check(sb.toString(), null);
+    }
+
+    public void testLarge10() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            testLargeIn();
+            cf.recycle();
+        }
+    }
+
+    public void testLargeIn() throws Exception {
+        StringBuffer sb = new StringBuffer();
+        Random r = new Random();
+        for (int i = 0; i < 16 * 2 * 1024; i++) {
+            sb.append(' ' + r.nextInt(32));
+        }
+        check(sb.toString(), null);
+    }
+
+
+    public void testSpdy() throws Exception {
+        cf.setDictionary(SpdyConnection.SPDY_DICT, SpdyConnection.DICT_ID);
+        check("connection: close\n", null);
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/DispatcherTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/DispatcherTest.java
new file mode 100644
index 0000000..f474007
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/DispatcherTest.java
@@ -0,0 +1,46 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import org.apache.tomcat.lite.io.CBuffer;
+
+import junit.framework.TestCase;
+
+public class DispatcherTest extends TestCase {
+
+    public void testMapper() throws Exception {
+        BaseMapper mapper = new BaseMapper();
+
+        String[] welcomes = new String[2];
+        welcomes[0] = "index.html";
+        welcomes[1] = "foo.html";
+
+        mapper.addContext("test1.com", "", "context0", new String[0], null, null);
+        mapper.addContext("test1.com", "/foo", "context1", new String[0], null, null);
+        mapper.addContext("test1.com", "/foo/bar", "context2", welcomes, null, null);
+        mapper.addContext("test1.com", "/foo/bar/bla", "context3", new String[0], null, null);
+
+        mapper.addWrapper("test1.com", "/foo/bar", "/fo/*", "wrapper0");
+        mapper.addWrapper("test1.com", "/foo/bar", "/", "wrapper1");
+        mapper.addWrapper("test1.com", "/foo/bar", "/blh", "wrapper2");
+        mapper.addWrapper("test1.com", "/foo/bar", "*.jsp", "wrapper3");
+        mapper.addWrapper("test1.com", "/foo/bar", "/blah/bou/*", "wrapper4");
+        mapper.addWrapper("test1.com", "/foo/bar", "/blah/bobou/*", "wrapper5");
+        mapper.addWrapper("test1.com", "/foo/bar", "*.htm", "wrapper6");
+
+        mapper.addContext("asdf.com", "", "context0", new String[0], null, null);
+
+        MappingData mappingData = new MappingData();
+
+        CBuffer host = CBuffer.newInstance();
+        host.set("test1.com");
+
+        CBuffer uri = CBuffer.newInstance();
+        uri.set("/foo/bar/blah/bobou/foo");
+
+        mapper.map(host, uri, mappingData);
+
+        assertEquals("context2", mappingData.context.toString());
+        assertEquals("/foo/bar", mappingData.contextPath.toString());
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelInMemoryTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelInMemoryTest.java
new file mode 100644
index 0000000..f0b2cf2
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelInMemoryTest.java
@@ -0,0 +1,384 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.CBuffer;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.IOChannel;
+import org.apache.tomcat.lite.io.IOConnector;
+import org.apache.tomcat.lite.io.MemoryIOConnector;
+import org.apache.tomcat.lite.io.MemoryIOConnector.MemoryIOChannel;
+
+// TODO: rename to Http11ConnectionTest
+public class HttpChannelInMemoryTest extends TestCase {
+    /**
+     *  Connection under test
+     */
+    Http11Connection conn;
+
+    /**
+     * Last http channel created by the connection
+     */
+    volatile HttpChannel http;
+
+    // Input/output for the connection
+    MemoryIOConnector.MemoryIOChannel net = new MemoryIOChannel();
+
+    HttpConnector serverConnector = new HttpConnector(null);
+
+    // Callback results for callback tests
+    boolean hasBody = false;
+    boolean bodyDone = false;
+    boolean bodySentDone = false;
+    boolean headersDone = false;
+    boolean allDone = false;
+
+    public void setUp() throws IOException {
+        // Requests will not be serviced - you must manually generate
+        // the response.
+        serverConnector.setHttpService(null);
+
+        conn = new Http11Connection(serverConnector) {
+            protected HttpChannel checkHttpChannel() throws IOException {
+                return http = super.checkHttpChannel();
+            }
+        }.serverMode();
+        conn.setSink(net);
+    }
+
+
+    public void test2Req() throws IOException {
+        String req = "GET /index.html?q=b&c=d HTTP/1.1\r\n" +
+        "Host:  Foo.com \n" +
+        "H2:Bar\r\n" +
+        "H3: Foo \r\n" +
+        " Bar\r\n" +
+        "H4: Foo\n" +
+        "    Bar\n" +
+        "\r\n" +
+        "HEAD /r2? HTTP/1.1\n" +
+        "Host: Foo.com\r\n" +
+        "H3: Foo \r\n" +
+        "       Bar\r\n" +
+        "H4: Foo\n" +
+        " Bar\n" +
+        "\r\n";
+        net.getIn().append(req);
+
+        //http = lastServer.get(0);
+        assertTrue(http.getRequest().method().equals("GET"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.1"));
+        assertEquals(http.getRequest().getMimeHeaders().size(), 4);
+        assertEquals(http.getRequest().getMimeHeaders().getHeader("Host").toString(),
+                "Foo.com");
+        assertEquals(http.getRequest().getMimeHeaders().getHeader("H2").toString(),
+                "Bar");
+
+        http.getOut().append("Response1");
+        http.getOut().close();
+        http.startSending();
+        http.release();
+
+        // now second response must be in.
+        // the connector will create a new http channel
+
+        //http = lastServer.get(1);
+
+        assertTrue(http.getRequest().method().equals("HEAD"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.1"));
+        assertTrue(http.getRequest().getMimeHeaders().size() == 3);
+        assertTrue(http.getRequest().getMimeHeaders().getHeader("Host")
+                .equals("Foo.com"));
+    }
+
+    public void testHttp11Close() throws IOException {
+        String req = "GET /index.html?q=b&c=d HTTP/1.1\r\n" +
+        "Host:  Foo.com\n" +
+        "Connection: close\n" +
+        "\n";
+        net.getIn().append(req);
+
+        assertTrue(http.getRequest().method().equals("GET"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.1"));
+
+        http.getOut().append("Response1");
+        http.getOut().close();
+        http.startSending();
+        http.release();
+
+        assertTrue(net.out.indexOf("connection:close") > 0);
+        assertFalse(net.isOpen());
+    }
+
+    public void testHttp10Close() throws IOException {
+        String req = "GET /index.html?q=b&c=d HTTP/1.0\r\n" +
+        "Host:  Foo.com \n" +
+        "\r\n";
+        net.getIn().append(req);
+
+        assertTrue(http.getRequest().method().equals("GET"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.0"));
+
+        http.getOut().append("Response1");
+        http.getOut().close();
+        http.startSending();
+
+        assertTrue(net.out.indexOf("connection:close") > 0);
+        assertFalse(net.isOpen());
+    }
+
+    public void testHttp10KA() throws IOException {
+        String req = "GET /index.html?q=b&c=d HTTP/1.0\r\n" +
+        "Connection: Keep-Alive\n" +
+        "Host:  Foo.com \n" +
+        "\r\n";
+        net.getIn().append(req);
+
+        assertTrue(http.getRequest().method().equals("GET"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.0"));
+
+        http.getOut().append("Hi");
+        http.getOut().close();
+        http.startSending();
+
+        // after request
+        assertEquals(conn.activeHttp, null);
+
+        assertTrue(net.out.indexOf("connection:keep-alive") > 0);
+        assertTrue(net.isOpen());
+        // inserted since we can calculate the response
+        assertEquals(http.getResponse().getHeader("Content-Length"),
+                   "2");
+    }
+
+    public void testHttp10KANoCL() throws IOException {
+        String req = "GET /index.html?q=b&c=d HTTP/1.0\r\n" +
+        "Connection: Keep-Alive\n" +
+        "Host:  Foo.com \n" +
+        "\r\n";
+        net.getIn().append(req);
+
+        assertTrue(http.getRequest().method().equals("GET"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.0"));
+
+        http.getOut().append("Hi");
+        http.startSending();
+
+        http.getOut().append("After");
+        http.getOut().close();
+        http.startSending();
+
+        // after request
+        assertEquals(conn.activeHttp, null);
+
+        assertFalse(net.out.indexOf("connection:keep-alive") > 0);
+        assertFalse(net.isOpen());
+        // inserted since we can calculate the response
+        assertEquals(http.getResponse().getHeader("Content-Length"),
+                null);
+        assertEquals(http.getResponse().getHeader("Transfer-Encoding"),
+                null);
+    }
+
+    public void testMultiLineHead() throws IOException {
+        net.getIn().append("GET / HTTP/1.0\n" +
+                "Cookie: 1234\n" +
+                "  456 \n" +
+                "Connection:   Close\n\n");
+        net.getIn().close();
+
+        MultiMap headers = http.getRequest().getMimeHeaders();
+        CBuffer cookie = headers.getHeader("Cookie");
+        CBuffer conn = headers.getHeader("Connection");
+        assertEquals(conn.toString(), "close");
+        assertEquals(cookie.toString(), "1234 456");
+
+        assertEquals(http.conn.headRecvBuf.toString(),
+                "GET / HTTP/1.0\n" +
+                "Cookie: 1234 456   \n" + // \n -> trailing space
+                "Connection:   Close\n\n");
+    }
+
+    public void testCloseSocket() throws IOException {
+        net.getIn().append("GET / HTTP/1.1\n"
+                + "Host: localhost\n"
+                + "\n");
+        assertTrue(((Http11Connection)http.conn).keepAlive());
+
+        net.getIn().close();
+        assertFalse(((Http11Connection)http.conn).keepAlive());
+    }
+
+    public void test2ReqByte2Byte() throws IOException {
+        String req = "GET /index.html?q=b&c=d HTTP/1.1\r\n" +
+        "Host:  Foo.com \n" +
+        "H2:Bar\r\n" +
+        "H3: Foo \r\n" +
+        " Bar\r\n" +
+        "H4: Foo\n" +
+        "    Bar\n" +
+        "\r\n" +
+        "HEAD /r2? HTTP/1.1\n" +
+        "Host: Foo1.com\n" +
+        "H3: Foo \r\n" +
+        "       Bar\r\n" +
+        "\r\n";
+        for (int i = 0; i < req.length(); i++) {
+            net.getIn().append(req.charAt(i));
+        }
+
+        assertTrue(http.getRequest().method().equals("GET"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.1"));
+        assertTrue(http.getRequest().getMimeHeaders().size() == 4);
+        assertTrue(http.getRequest().getMimeHeaders().getHeader("Host")
+                .equals("Foo.com"));
+
+        // send a response
+        http.sendBody.append("Response1");
+        http.getOut().close();
+
+        http.startSending(); // This will trigger a pipelined request
+
+        http.release(); // now second response must be in
+
+        assertTrue(http.getRequest().method().equals("HEAD"));
+        assertTrue(http.getRequest().protocol().equals("HTTP/1.1"));
+        assertTrue(http.getRequest().getMimeHeaders().size() == 2);
+        assertTrue(http.getRequest().getMimeHeaders().getHeader("Host")
+                .equals("Foo1.com"));
+
+        // send a response - service method will be called
+        http.sendBody.append("Response2");
+        http.getOut().close();
+        http.release(); // now second response must be in
+
+
+    }
+
+    public void testEndWithoutFlushCallbacks() throws IOException {
+
+        net.getIn().append(POST);
+
+        net.getIn().close();
+        http.setCompletedCallback(new RequestCompleted() {
+            public void handle(HttpChannel data, Object extra)
+            throws IOException {
+                allDone = true;
+            }
+        });
+
+        http.sendBody.queue("Hi");
+        http.getOut().close();
+        http.startSending(); // will call handleEndSend
+
+        assertTrue(allDone);
+
+    }
+
+    public void testCallbacks() throws IOException {
+        // already accepted - will change
+        serverConnector.setHttpService(new HttpService() {
+            public void service(HttpRequest httpReq, HttpResponse httpRes)
+                    throws IOException {
+
+                headersDone = true;
+                HttpChannel http = httpReq.getHttpChannel();
+
+                http.setCompletedCallback(new RequestCompleted() {
+                    public void handle(HttpChannel data, Object extra)
+                    throws IOException {
+                        allDone = true;
+                    }
+                });
+                http.setDataReceivedCallback(new IOConnector.DataReceivedCallback() {
+                    @Override
+                    public void handleReceived(IOChannel ch) throws IOException {
+                        if (ch.getIn().isAppendClosed()) {
+                            bodyDone = true;
+                        }
+                    }
+                });
+                http.setDataFlushedCallback(new IOConnector.DataFlushedCallback() {
+                    @Override
+                    public void handleFlushed(IOChannel ch) throws IOException {
+                        if (ch.getOut().isAppendClosed()) {
+                            bodySentDone = true;
+                        }
+                    }
+                });
+            }
+        });
+
+        // Inject the request
+        net.getIn().append("POST / HTTP/1.0\n" +
+                "Connection: Close\n" +
+                "Content-Length: 4\n\n" +
+                "1");
+        assertTrue(headersDone);
+        net.getIn().append("234");
+
+        net.getIn().close();
+        assertTrue(bodyDone);
+
+
+        http.sendBody.queue("Hi");
+        http.getOut().close();
+        http.startSending();
+        assertTrue(bodySentDone);
+
+        assertTrue(allDone);
+
+    }
+
+    public static String POST = "POST / HTTP/1.0\n" +
+        "Connection: Close\n" +
+        "Content-Length: 4\n\n" +
+        "1234";
+
+    public void testClose() throws IOException {
+        net.getIn().append(POST);
+        net.getIn().close();
+
+        IOBuffer receiveBody = http.receiveBody;
+        IOBuffer appData = receiveBody;
+        BBuffer res = BBuffer.allocate(1000);
+        appData.readAll(res);
+
+        assertEquals(res.toString(), "1234");
+        assertFalse(((Http11Connection)http.conn).keepAlive());
+
+        http.sendBody.queue(res);
+        http.getOut().close();
+        http.startSending();
+
+        assertTrue(net.getOut().isAppendClosed());
+        assertTrue(net.out.toString().indexOf("\n1234") > 0);
+
+    }
+
+    public void testReadLine() throws Exception {
+        net.getIn().append("POST / HTTP/1.0\n" +
+        		"Content-Length: 28\n\n" +
+                "Line 1\n" +
+                "Line 2\r\n" +
+                "Line 3\r" +
+                "Line 4");
+        net.getIn().close();
+
+        BufferedReader r = http.getRequest().getReader();
+        assertEquals("Line 1", r.readLine());
+        assertEquals("Line 2", r.readLine());
+        assertEquals("Line 3", r.readLine());
+        assertEquals("Line 4", r.readLine());
+        assertEquals(null, r.readLine());
+
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelTest.java
new file mode 100644
index 0000000..ad13187
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpChannelTest.java
@@ -0,0 +1,128 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.io.BBuffer;
+
+public class HttpChannelTest extends TestCase {
+
+    HttpChannel ch = new HttpChannel().serverMode(true);
+    Http11Connection con = new Http11Connection(null).serverMode();
+    HttpRequest req = ch.getRequest();
+
+
+    BBuffer head = BBuffer.allocate();
+    BBuffer line = BBuffer.wrapper();
+    BBuffer name = BBuffer.wrapper();
+    BBuffer value = BBuffer.wrapper();
+
+    BBuffer statusB = BBuffer.wrapper();
+    BBuffer msgB = BBuffer.wrapper();
+    BBuffer methodB = BBuffer.wrapper();
+    BBuffer queryB = BBuffer.wrapper("");
+    BBuffer requestB = BBuffer.wrapper();
+    BBuffer protoB = BBuffer.wrapper();
+
+    BBuffer l7 = BBuffer.wrapper("GET \n");
+    BBuffer l8 = BBuffer.wrapper("GET /\n");
+    BBuffer l9 = BBuffer.wrapper("GET /a?b\n");
+    BBuffer l10 = BBuffer.wrapper("GET /a?b HTTP/1.0\n");
+    BBuffer l11 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b");
+    BBuffer l12 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\n");
+
+    BBuffer f1 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\n\n");
+    BBuffer f2 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\r\n\r\n");
+    BBuffer f3 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\r\r");
+    BBuffer f4 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\r\n\r");
+
+    public void reqTest(String lineS, String method, String req,
+            String qry, String proto) throws IOException {
+        BBuffer line = BBuffer.wrapper(lineS);
+        queryB.recycle();
+        protoB.recycle();
+        requestB.recycle();
+        methodB.recycle();
+        con.parseRequestLine(line, methodB, requestB, queryB, protoB);
+        assertEquals(proto, protoB.toString());
+        assertEquals(req, requestB.toString());
+        assertEquals(qry, queryB.toString());
+        assertEquals(method, methodB.toString());
+    }
+
+    public void testParams() throws IOException {
+        MultiMap params = processQry("a=b&c=d");
+        assertEquals("b", params.getString("a"));
+    }
+
+    private MultiMap processQry(String qry) throws IOException {
+        BBuffer head = BBuffer.wrapper("GET /a?" + qry + " HTTP/1.0\n" +
+        		"Host: a\n\n");
+        con.parseMessage(ch, head);
+        MultiMap params = req.getParameters();
+        return params;
+    }
+
+    public void testParseReq() throws IOException {
+        reqTest("GET / HTTP/1.0", "GET", "/", "", "HTTP/1.0");
+        reqTest("GET", "GET", "", "", "");
+        reqTest("GET   / HTTP/1.0", "GET", "/", "", "HTTP/1.0");
+        reqTest("GET /     HTTP/1.0", "GET", "/", "", "HTTP/1.0");
+        reqTest("GET /?b HTTP/1.0", "GET", "/", "b", "HTTP/1.0");
+        reqTest("GET ?a HTTP/1.0", "GET", "", "a", "HTTP/1.0");
+        reqTest("GET a HTTP/1.0", "GET", "a", "", "HTTP/1.0");
+        reqTest("GET a? HTTP/1.0", "GET", "a", "", "HTTP/1.0");
+    }
+
+    public void headTest(String headS, String expName, String expValue,
+            String expLine, String expRest) throws IOException {
+        head = BBuffer.wrapper(headS);
+        head.readLine(line);
+        con.parseHeader(ch, head, line, name, value);
+
+        assertEquals(expName, name.toString());
+        assertEquals(expValue, value.toString());
+
+        assertEquals(expLine, line.toString());
+        assertEquals(expRest, head.toString());
+    }
+
+    public void testParseHeader() throws IOException {
+        headTest("a:b\n", "a", "b", "", "");
+        headTest("a :b\n", "a", "b", "", "");
+        headTest("a : b\n", "a", "b", "", "");
+        headTest("a :  b\n", "a", "b", "", "");
+        headTest("a :  b c \n", "a", "b c", "", "");
+        headTest("a :  b c\n", "a", "b c", "", "");
+        headTest("a :  b  c\n", "a", "b c", "", "");
+        headTest("a :  b  \n c\n", "a", "b c", "", "");
+        headTest("a :  b  \n  c\n", "a", "b c", "", "");
+        headTest("a :  b  \n  c\nd:", "a", "b c", "", "d:");
+
+    }
+
+    public void responseTest(String lineS, String proto, String status,
+            String msg) throws IOException {
+        protoB.recycle();
+        statusB.recycle();
+        msgB.recycle();
+        BBuffer line = BBuffer.wrapper(lineS);
+        con.parseResponseLine(line,
+                protoB, statusB, msgB);
+        assertEquals(proto, protoB.toString());
+        assertEquals(status, statusB.toString());
+        assertEquals(msg, msgB.toString());
+    }
+
+    public void testResponse() throws Exception {
+        responseTest("HTTP/1.1 200 OK", "HTTP/1.1", "200", "OK");
+        responseTest("HTTP/1.1  200 OK", "HTTP/1.1", "200", "OK");
+        responseTest("HTTP/1.1  200", "HTTP/1.1", "200", "");
+        responseTest("HTTP/1.1", "HTTP/1.1", "", "");
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java
new file mode 100644
index 0000000..bc42b3b
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/HttpsTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.http;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.TestMain;
+import org.apache.tomcat.lite.io.BBuffer;
+
+public class HttpsTest extends TestCase {
+
+    static int port = 8443;
+
+    public void testSimpleClient() throws Exception {
+        final HttpConnector httpClient = TestMain.shared().getClient();
+        checkResponse(httpClient);
+    }
+
+    public void testSimpleServer() throws Exception {
+        final HttpConnector httpClient = TestMain.shared().getClient();
+        BBuffer res = TestMain.getUrl("https://localhost:8443/hello");
+        assertTrue(res.toString().indexOf("Hello") >= 0);
+    }
+
+
+    private void checkResponse(HttpConnector httpCon) throws Exception {
+        HttpRequest ch = httpCon.request("localhost", port).setSecure(true);
+
+        ch.setRequestURI("/hello");
+        ch.setProtocol("HTTP/1.0"); // to force close
+        ch.send();
+        BBuffer res = ch.readAll();
+
+        assertTrue(res.toString().indexOf("Hello") >= 0);
+    }
+
+    public void testSimpleClient20() throws Exception {
+        final HttpConnector httpClient = TestMain.shared().getClient();
+        for (int i = 0; i < 10; i++) {
+            checkResponse(httpClient);
+        }
+    }
+
+    public void testSimpleRequestGoogle() throws Exception {
+        for (int i = 0; i < 40; i++) {
+        final HttpConnector httpClient = TestMain.shared().getClient();
+        HttpRequest client = httpClient.request("www.google.com", 443).
+            setSecure(true);
+        client.getHttpChannel().setIOTimeout(2000000);
+        client.setRequestURI("/accounts/ServiceLogin");
+        client.send();
+
+        BBuffer res = client.readAll();
+        assertTrue(res.toString().indexOf("<title>Google Accounts</title>") > 0);
+        }
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java
new file mode 100644
index 0000000..00e4eac
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/LiveHttp1Test.java
@@ -0,0 +1,152 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.http;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.TestMain;
+import org.apache.tomcat.lite.io.BBuffer;
+
+public class LiveHttp1Test extends TestCase {
+    // Proxy tests extend this class, run same tests via proxy on 8903
+    protected int clientPort = 8802;
+
+    HttpRequest httpReq;
+
+    BBuffer bodyRecvBuffer = BBuffer.allocate(1024);
+
+    int to = 1000000;
+
+    public void setUp() throws IOException {
+        // DefaultHttpConnector.get().setDebug(true);
+        // DefaultHttpConnector.get().setDebugHttp(true);
+        TestMain.getTestServer();
+
+        httpReq = HttpClient.newClient().request("localhost",
+                clientPort);
+
+        bodyRecvBuffer.recycle();
+    }
+
+    public void tearDown() throws Exception {
+        if (httpReq != null) {
+            httpReq.release(); // async
+            httpReq = null;
+        }
+    }
+
+    public void testSimpleRequest() throws Exception {
+        httpReq.requestURI().set("/hello");
+
+        httpReq.send();
+        httpReq.readAll(bodyRecvBuffer, to);
+        assertEquals("Hello world", bodyRecvBuffer.toString());
+    }
+
+    public void testSimpleRequestClose() throws Exception {
+        httpReq.requestURI().set("/hello");
+        httpReq.setHeader("Connection", "close");
+
+        httpReq.send();
+        httpReq.readAll(bodyRecvBuffer, to);
+        assertEquals("Hello world", bodyRecvBuffer.toString());
+    }
+
+    public void testPoolGetRelease() throws Exception {
+        HttpConnector con = HttpClient.newClient();
+        con.setMaxHttpPoolSize(10);
+        HttpChannel httpCh = con.get("localhost", clientPort);
+        httpCh.release();
+
+        httpCh = con.get("localhost", clientPort);
+        httpCh.release();
+
+        httpCh = con.get("localhost", clientPort);
+        httpCh.release();
+
+    }
+
+    public void testSimpleChunkedRequest() throws Exception {
+        httpReq.requestURI().set("/chunked/foo");
+        httpReq.send();
+        httpReq.readAll(bodyRecvBuffer, to);
+        assertTrue(bodyRecvBuffer.toString(), bodyRecvBuffer.toString().indexOf("AAA") >= 0);
+    }
+
+    // Check waitResponseHead()
+    public void testRequestHead() throws Exception {
+        httpReq.requestURI().set("/echo/foo");
+
+        // Send the request, wait response
+        httpReq.send();
+
+        httpReq.readAll(bodyRecvBuffer, to);
+        assertTrue(bodyRecvBuffer.toString().indexOf("GET /echo/foo") > 0);
+    }
+
+    public void test10() throws Exception {
+        for (int i = 0; i < 10; i++) {
+            testSimpleRequest();
+            tearDown();
+            setUp();
+
+            notFound();
+            tearDown();
+            setUp();
+
+            testSimpleRequest();
+            tearDown();
+            setUp();
+        }
+    }
+
+    public void notFound() throws Exception {
+        httpReq.requestURI().set("/foo");
+        httpReq.send();
+        httpReq.readAll(bodyRecvBuffer, to);
+    }
+
+    // compression not implemented
+    public void testGzipRequest() throws Exception {
+        httpReq.requestURI().set("/hello");
+        httpReq.setHeader("accept-encoding",
+            "gzip");
+
+        // Send the request, wait response
+        httpReq.send();
+        // cstate.waitResponseHead(10000); // headers are received
+        // ByteChunk data = new ByteChunk(1024);
+        // acstate.serializeResponse(acstate.res, data);
+
+        // System.err.println(bodyRecvBuffer.toString());
+
+        httpReq.readAll(bodyRecvBuffer, to);
+        // Done
+    }
+
+    public void testWrongPort() throws Exception {
+        httpReq = HttpClient.newClient().request("localhost", 18904);
+        httpReq.requestURI().set("/hello");
+
+        httpReq.send();
+
+        httpReq.readAll(bodyRecvBuffer, to);
+        assertEquals(0, bodyRecvBuffer.remaining());
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/MultiMapTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/MultiMapTest.java
new file mode 100644
index 0000000..e222f36
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/MultiMapTest.java
@@ -0,0 +1,54 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import org.apache.tomcat.lite.http.MultiMap.Entry;
+
+import junit.framework.TestCase;
+
+public class MultiMapTest extends TestCase {
+
+    MultiMap map = new MultiMap();
+    MultiMap lmap = new MultiMap().insensitive();
+
+    public void testAdd() {
+        map.add("foo", "bar");
+        assertEquals("bar", map.get("foo").toString());
+    }
+
+    public void testRemove() {
+        map.add("foo", "bar");
+        map.add("foo", "bar");
+        map.add("foo1", "bar");
+        assertEquals(3, map.count);
+        map.remove("foo");
+        assertEquals(1, map.count);
+    }
+
+    public void testRemove1() {
+        map.add("foo", "bar");
+        map.add("foo1", "bar");
+        map.add("foo", "bar");
+        assertEquals(3, map.count);
+        map.remove("foo");
+        assertEquals(1, map.count);
+        map.remove("foo1");
+        assertEquals(0, map.count);
+    }
+
+    public void testCase() {
+        lmap.add("foo", "bar1");
+        lmap.add("Foo", "bar2");
+        lmap.add("a", "bar3");
+        lmap.add("B", "bar4");
+        assertEquals(4, lmap.count);
+        assertEquals(3, lmap.map.size());
+
+        assertEquals("bar3", lmap.getString("a"));
+        assertEquals("bar3", lmap.getString("A"));
+        assertEquals("bar1", lmap.getString("Foo"));
+        Entry entry = lmap.getEntry("FOO");
+        assertEquals(2, entry.values.size());
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java
new file mode 100644
index 0000000..3b51e10
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/SpdyTest.java
@@ -0,0 +1,165 @@
+/*
+ */
+package org.apache.tomcat.lite.http;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.TestMain;
+import org.apache.tomcat.lite.http.HttpConnectionPool.RemoteServer;
+import org.apache.tomcat.lite.io.IOBuffer;
+import org.apache.tomcat.lite.io.SocketConnector;
+
+public class SpdyTest extends TestCase {
+    HttpConnector http11Con = TestMain.shared().getClient();
+
+    static HttpConnector spdyCon = HttpClient.newClient();
+
+    static HttpConnector spdyConSsl = HttpClient.newClient();
+
+    HttpConnector memSpdyCon = new HttpConnector(null);
+
+    public void testClient() throws IOException {
+        HttpRequest req =
+            spdyCon.request("http://localhost:8802/echo/test1");
+
+        // Force SPDY - no negotiation
+        req.setProtocol("SPDY/1.0");
+
+        HttpResponse res = req.waitResponse();
+
+        assertEquals(200, res.getStatus());
+        //assertEquals("", res.getHeader(""));
+
+        BufferedReader reader = res.getReader();
+        String line1 = reader.readLine();
+        //assertEquals("", line1);
+    }
+
+    public void testSslClient() throws IOException {
+
+        HttpRequest req =
+            spdyConSsl.request("http://localhost:8443/echo/test1");
+        // Enable SSL for the connection.
+        // TODO: this must be done on the first request, all will be
+        // encrypted.
+        req.setSecure(true);
+        // Force SPDY - no negotiation
+        req.setProtocol("SPDY/1.0");
+
+        HttpResponse res = req.waitResponse();
+
+        assertEquals(200, res.getStatus());
+        //assertEquals("", res.getHeader(""));
+
+        res.setReadTimeout(2000);
+        BufferedReader reader = res.getReader();
+        String line1 = reader.readLine();
+        //assertEquals("", line1);
+    }
+
+
+    // Initial frame generated by Chrome
+    public void testParse() throws IOException {
+            InputStream is =
+            getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreq0.bin");
+
+        IOBuffer iob = new IOBuffer();
+        iob.append(is);
+
+        SpdyConnection con = new SpdyConnection(memSpdyCon, new RemoteServer());
+
+        // By default it has a dispatcher buit-in
+        con.serverMode = true;
+
+        con.dataReceived(iob);
+
+        HttpChannel spdyChannel = con.channels.get(1);
+
+        assertEquals(1, con.lastFrame.version);
+        assertEquals(1, con.lastFrame.type);
+        assertEquals(1, con.lastFrame.flags);
+
+        assertEquals(417, con.lastFrame.length);
+
+        // TODO: test req, headers
+        HttpRequest req = spdyChannel.getRequest();
+        assertTrue(req.getHeader("accept").indexOf("application/xml") >= 0);
+
+    }
+
+    // Initial frame generated by Chrome
+    public void testParseCompressed() throws IOException {
+        InputStream is =
+            getClass().getClassLoader().getResourceAsStream("org/apache/tomcat/lite/http/spdyreqCompressed.bin");
+
+        IOBuffer iob = new IOBuffer();
+        iob.append(is);
+
+        SpdyConnection con = new SpdyConnection(memSpdyCon, new RemoteServer());
+
+        // By default it has a dispatcher buit-in
+        con.serverMode = true;
+
+        con.dataReceived(iob);
+
+        HttpChannel spdyChannel = con.channels.get(1);
+
+        assertEquals(1, con.lastFrame.version);
+        assertEquals(1, con.lastFrame.type);
+        assertEquals(1, con.lastFrame.flags);
+
+        // TODO: test req, headers
+        HttpRequest req = spdyChannel.getRequest();
+        assertTrue(req.getHeader("accept").indexOf("application/xml") >= 0);
+
+    }
+
+    // Does int parsing works ?
+    public void testLargeInt() throws Exception {
+
+        IOBuffer iob = new IOBuffer();
+        iob.append(0x80);
+        iob.append(0x01);
+        iob.append(0xFF);
+        iob.append(0xFF);
+
+        iob.append(0xFF);
+        iob.append(0xFF);
+        iob.append(0xFF);
+        iob.append(0xFF);
+
+        SpdyConnection con = new SpdyConnection(memSpdyCon, new RemoteServer());
+        con.dataReceived(iob);
+        assertEquals(1, con.currentInFrame.version);
+        assertEquals(0xFFFF, con.currentInFrame.type);
+        assertEquals(0xFF, con.currentInFrame.flags);
+        assertEquals(0xFFFFFF, con.currentInFrame.length);
+
+    }
+
+    // Does int parsing works ?
+    public void testBad() throws Exception {
+
+            IOBuffer iob = new IOBuffer();
+            iob.append(0xFF);
+            iob.append(0xFF);
+            iob.append(0xFF);
+            iob.append(0xFF);
+
+            iob.append(0xFF);
+            iob.append(0xFF);
+            iob.append(0xFF);
+            iob.append(0xFF);
+
+            SpdyConnection con = new SpdyConnection(memSpdyCon, new RemoteServer());
+            con.dataReceived(iob);
+
+            assertEquals(1, con.streamErrors.get());
+
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/genrsa_512.cert b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/genrsa_512.cert
new file mode 100644
index 0000000..8b22354
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/genrsa_512.cert
@@ -0,0 +1,15 @@
+-----BEGIN CERTIFICATE-----
+MIICXzCCAgmgAwIBAgIJAKl2huIxu+mhMA0GCSqGSIb3DQEBBQUAMFYxCzAJBgNV
+BAYTAlVTMQswCQYDVQQIEwJDQTESMBAGA1UEChMJbG9jYWxob3N0MRIwEAYDVQQL
+Ewlsb2NhbGhvc3QxEjAQBgNVBAMTCWxvY2FsaG9zdDAeFw0xMDAyMjgwNTM1NDVa
+Fw0xMTAyMjgwNTM1NDVaMFYxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJDQTESMBAG
+A1UEChMJbG9jYWxob3N0MRIwEAYDVQQLEwlsb2NhbGhvc3QxEjAQBgNVBAMTCWxv
+Y2FsaG9zdDBcMA0GCSqGSIb3DQEBAQUAA0sAMEgCQQCkMTUYO8aijGGXIUTMRp3h
+NTBudLHDF66MDQxomS+drL1sDGzpsLG1ltZcSPrp2Ak2tDVLd9eDb6VmddgyvE0N
+AgMBAAGjgbkwgbYwHQYDVR0OBBYEFPfuRg3nOxppwBPdpOQTbjDbcPbEMIGGBgNV
+HSMEfzB9gBT37kYN5zsaacAT3aTkE24w23D2xKFapFgwVjELMAkGA1UEBhMCVVMx
+CzAJBgNVBAgTAkNBMRIwEAYDVQQKEwlsb2NhbGhvc3QxEjAQBgNVBAsTCWxvY2Fs
+aG9zdDESMBAGA1UEAxMJbG9jYWxob3N0ggkAqXaG4jG76aEwDAYDVR0TBAUwAwEB
+/zANBgkqhkiG9w0BAQUFAANBAFqvhtdEd1OKNZ2TbLeFJbvavSZUDd0d/vz+qZgh
+L2Oe+CU/82D4lVpf52g4HFMrnEnAiJFuq1SwoNJZzlxk8kU=
+-----END CERTIFICATE-----
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/genrsa_512.der b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/genrsa_512.der
new file mode 100644
index 0000000..75ef449
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/genrsa_512.der
Binary files differ
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/EchoCallback.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/EchoCallback.java
new file mode 100644
index 0000000..bd614ec
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/EchoCallback.java
@@ -0,0 +1,61 @@
+/*  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.http.services;
+
+import java.io.IOException;
+import java.util.logging.Logger;
+
+import org.apache.tomcat.lite.http.Http11Connection;
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.http.HttpChannel.HttpService;
+import org.apache.tomcat.lite.io.IOBuffer;
+
+/**
+ * Response is plain/text, copy of the received request
+ */
+public class EchoCallback implements HttpService {
+    Logger log = Logger.getLogger("coyote.static");
+
+    String contentType = "text/plain";
+
+
+    public EchoCallback() {
+    }
+
+    @Override
+    public void service(HttpRequest req, HttpResponse res) throws IOException {
+        HttpChannel sproc = req.getHttpChannel();
+        res.setStatus(200);
+        res.setContentType(contentType);
+
+        IOBuffer tmp = new IOBuffer(null);
+        Http11Connection.serialize(req, tmp);
+
+        sproc.getOut().append("REQ HEAD:\n");
+        sproc.getOut().append(tmp.readAll(null));
+        IOBuffer reqBuf = sproc.getOut();
+
+        reqBuf.append("\nCONTENT_LENGTH:")
+            .append(Long.toString(req.getContentLength()))
+            .append("\n");
+//
+//        sproc.release();
+    }
+
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/SleepCallback.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/SleepCallback.java
new file mode 100644
index 0000000..5bb174b
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/services/SleepCallback.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.http.services;
+
+import java.io.IOException;
+
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpResponse;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.proxy.StaticContentService;
+
+/**
+ * Test adapters that sleeps, for load test and for encoding.
+ * REQUIRES THREAD POOL
+ */
+public class SleepCallback extends StaticContentService {
+  long t1;
+  long t2;
+  long t3;
+  long t4;
+
+  public SleepCallback() {
+  }
+
+  public SleepCallback sleep(long t1, long t2, long t3,
+                            long t4) {
+    this.t1 = t1;
+    this.t2 = t2;
+    this.t3 = t3;
+    this.t4 = t4;
+    return this;
+  }
+
+  public SleepCallback sleep(long t1) {
+    return sleep(t1, t1, t1, t1);
+  }
+
+  @Override
+  public void service(HttpRequest httpReq, HttpResponse res) throws IOException {
+      // TODO: blocking ! needs thread pool !
+    try {
+
+        Thread.currentThread().sleep(t1);
+        res.setStatus(200);
+        if (!chunked) {
+            res.setContentLength(mb.remaining() * 2);
+        }
+        res.setContentType(contentType);
+
+        res.flush();
+
+        Thread.currentThread().sleep(t2);
+
+        res.getBody().queue(BBuffer.wrapper(mb, 0, mb.remaining()));
+        res.flush();
+
+        //res.action(ActionCode.ACTION_CLIENT_FLUSH, res);
+
+        Thread.currentThread().sleep(t3);
+
+        res.getBody().queue(BBuffer.wrapper(mb, 0, mb.remaining()));
+        res.flush();
+
+        Thread.currentThread().sleep(t4);
+    } catch (InterruptedException e) {
+        // TODO Auto-generated catch block
+        e.printStackTrace();
+    }
+  }
+
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0 b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0
new file mode 100644
index 0000000..4df3aa9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0
Binary files differ
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0.bin b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0.bin
new file mode 100644
index 0000000..4df3aa9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreq0.bin
Binary files differ
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreqCompressed b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreqCompressed
new file mode 100644
index 0000000..3507ff9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreqCompressed
Binary files differ
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreqCompressed.bin b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreqCompressed.bin
new file mode 100644
index 0000000..3507ff9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/http/spdyreqCompressed.bin
Binary files differ
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/io/BBufferTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/BBufferTest.java
new file mode 100644
index 0000000..ec7c9e9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/BBufferTest.java
@@ -0,0 +1,159 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import org.apache.tomcat.lite.io.BBuffer;
+
+import junit.framework.TestCase;
+
+public class BBufferTest extends TestCase {
+    BBuffer res = BBuffer.wrapper("");
+
+    BBuffer l1 = BBuffer.wrapper("");
+    BBuffer l1a = BBuffer.wrapper("a");
+
+    BBuffer l2 = BBuffer.wrapper("\r");
+    BBuffer l3 = BBuffer.wrapper("\n");
+    BBuffer l4 = BBuffer.wrapper("\r\n");
+    BBuffer l5 = BBuffer.wrapper("\r\na");
+    BBuffer l5_a = BBuffer.wrapper("\ra");
+    BBuffer l5_b = BBuffer.wrapper("\na");
+    BBuffer l6 = BBuffer.wrapper("a\n");
+    BBuffer l7 = BBuffer.wrapper("GET \n");
+    BBuffer l8 = BBuffer.wrapper("GET /\n");
+    BBuffer l9 = BBuffer.wrapper("GET /a?b\n");
+    BBuffer l10 = BBuffer.wrapper("GET /a?b HTTP/1.0\n");
+    BBuffer l11 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b");
+    BBuffer l12 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\n");
+
+    BBuffer f1 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\n\n");
+    BBuffer f2 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\r\n\r\n");
+    BBuffer f3 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\r\r");
+    BBuffer f4 = BBuffer.wrapper("GET /a?b HTTP/1.0\na:b\r\n\r");
+
+    BBuffer s1 = BBuffer.wrapper(" \n");
+    BBuffer s2 = BBuffer.wrapper(" a");
+    BBuffer s3 = BBuffer.wrapper("  ");
+    BBuffer s4 = BBuffer.wrapper("   a");
+
+    BBuffer h1 = BBuffer.wrapper("a");
+    BBuffer h2 = BBuffer.wrapper("a?b");
+    BBuffer h3 = BBuffer.wrapper("a b");
+
+    public void hashTest(String s) {
+        assertEquals(s.hashCode(), BBuffer.wrapper(s).hashCode());
+    }
+
+    public void testHash() {
+        hashTest("");
+        hashTest("a");
+        hashTest("123abc");
+        hashTest("123abc\0");
+        // Fails for UTF chars - only ascii hashTest("123abc\u12345;");
+    }
+
+    public void testReadToSpace() {
+        assertEquals(3, l8.readToSpace(res));
+        assertEquals("GET", res.toString());
+        assertEquals(" /\n", l8.toString());
+
+        assertEquals(0, l1.readToSpace(res));
+        assertEquals("", res.toString());
+        assertEquals("", l1.toString());
+    }
+
+    public void testReadToDelim() {
+        assertEquals(1, h1.readToDelimOrSpace((byte)'?', res));
+        assertEquals("a", res.toString());
+        assertEquals("", h1.toString());
+
+        assertEquals(1, h2.readToDelimOrSpace((byte)'?', res));
+        assertEquals("a", res.toString());
+        assertEquals("?b", h2.toString());
+
+        assertEquals(1, h3.readToDelimOrSpace((byte)'?', res));
+        assertEquals("a", res.toString());
+        assertEquals(" b", h3.toString());
+    }
+
+    public void testGet() {
+        assertEquals(0x20, s1.get(0));
+        assertEquals(0x0a, s1.get(1));
+        try {
+            s1.get(2);
+        } catch(ArrayIndexOutOfBoundsException ex) {
+            return;
+        }
+        fail("Exception");
+    }
+
+    public void testSkipSpace() {
+        assertEquals(1, s1.skipSpace());
+        assertEquals("\n", s1.toString());
+
+        assertEquals(1, s2.skipSpace());
+        assertEquals("a", s2.toString());
+
+        assertEquals(2, s3.skipSpace());
+        assertEquals("", s3.toString());
+
+        assertEquals(3, s4.skipSpace());
+        assertEquals("a", s4.toString());
+
+        assertEquals(0, l1.skipSpace());
+        assertEquals("", l1.toString());
+    }
+
+    public void testLFLF() {
+        assertTrue(f1.hasLFLF());
+        assertTrue(f2.hasLFLF());
+        assertTrue(f3.hasLFLF());
+
+        assertFalse(f4.hasLFLF());
+        assertFalse(l1.hasLFLF());
+        assertFalse(l2.hasLFLF());
+        assertFalse(l3.hasLFLF());
+
+        assertFalse(l10.hasLFLF());
+        assertFalse(l11.hasLFLF());
+        assertFalse(l12.hasLFLF());
+    }
+
+    public void testReadLine() {
+        assertEquals(-1, l1.readLine(res));
+        assertEquals("", res.toString());
+        assertEquals("", l1.toString());
+
+        assertEquals(-1, l1a.readLine(res));
+        assertEquals("", res.toString());
+        assertEquals("a", l1a.toString());
+
+        assertEquals(0, l2.readLine(res));
+        assertEquals("", l2.toString());
+        assertEquals("", res.toString());
+        assertEquals(0, l3.readLine(res));
+        assertEquals("", l3.toString());
+        assertEquals("", res.toString());
+        assertEquals(0, l4.readLine(res));
+        assertEquals("", res.toString());
+
+        assertEquals(0, l5.readLine(res));
+        assertEquals("", res.toString());
+        assertEquals("a", l5.toString());
+        assertEquals(0, l5_b.readLine(res));
+        assertEquals("", res.toString());
+        assertEquals("a", l5_b.toString());
+        assertEquals(0, l5_a.readLine(res));
+        assertEquals("", res.toString());
+        assertEquals("a", l5_a.toString());
+
+        assertEquals(1, l6.readLine(res));
+        assertEquals("a", res.toString());
+
+        assertEquals(4, l7.readLine(res));
+        assertEquals("GET ", res.toString());
+        assertEquals(5, l8.readLine(res));
+        assertEquals("GET /", res.toString());
+
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/io/CBufferTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/CBufferTest.java
new file mode 100644
index 0000000..ab064d9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/CBufferTest.java
@@ -0,0 +1,32 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import junit.framework.TestCase;
+
+public class CBufferTest extends TestCase {
+
+    CBuffer ext = CBuffer.newInstance();
+
+    public void extTest(String path, String exp) {
+        CBuffer.newInstance().append(path).getExtension(ext, '/', '.');
+        assertEquals(exp, ext.toString());
+    }
+
+    public void testExt() {
+        extTest("foo.jsp", "jsp");
+        extTest("foo.j", "j");
+        extTest("/foo.j", "j");
+        extTest("//foo.j", "j");
+        extTest(".j", "j");
+        extTest(".", "");
+        extTest("/abc", "");
+        extTest("/abc.", "");
+        extTest("/abc/", "");
+        extTest("/abc/d", "");
+    }
+
+    public void testLastIndexOf() {
+
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/io/OneTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/OneTest.java
new file mode 100644
index 0000000..eb5202a
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/OneTest.java
@@ -0,0 +1,27 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+
+import org.apache.tomcat.lite.TestMain;
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.io.MemoryIOConnector;
+import org.apache.tomcat.lite.io.MemoryIOConnector.MemoryIOChannel;
+
+import junit.framework.TestCase;
+
+public class OneTest extends TestCase {
+
+    public void setUp() throws Exception {
+        TestMain.getTestServer();
+    }
+
+    public void tearDown() throws IOException {
+    }
+
+
+    public void testOne() throws Exception {
+
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/io/SocksTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/SocksTest.java
new file mode 100644
index 0000000..3a1443d
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/SocksTest.java
@@ -0,0 +1,58 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import java.io.IOException;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.Proxy;
+import java.net.ProxySelector;
+import java.net.SocketAddress;
+import java.net.URI;
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.tomcat.lite.proxy.SocksServer;
+
+
+import junit.framework.TestCase;
+
+public class SocksTest extends TestCase {
+
+    public void setUp() {
+//        SocksServer socks = new SocksServer();
+//        try {
+//            socks.initServer();
+//        } catch (IOException e1) {
+//            // TODO Auto-generated catch block
+//            e1.printStackTrace();
+//        }
+//
+//        ProxySelector.setDefault(new ProxySelector() {
+//
+//            @Override
+//            public void connectFailed(URI uri, SocketAddress sa, IOException ioe) {
+//            }
+//
+//            @Override
+//            public List<Proxy> select(URI uri) {
+//
+//                List<Proxy> res = new ArrayList<Proxy>();
+//                try {
+//                    res.add(new Proxy(Proxy.Type.SOCKS,
+//                            new InetSocketAddress(InetAddress.getLocalHost(), 1080)));
+//                } catch (UnknownHostException e) {
+//                    // TODO Auto-generated catch block
+//                    e.printStackTrace();
+//                }
+//                return res;
+//            }
+//
+//        });
+    }
+
+    public void testSocks() {
+
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/io/UEncoderTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/UEncoderTest.java
new file mode 100644
index 0000000..9f4ffcc
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/UEncoderTest.java
@@ -0,0 +1,44 @@
+/*
+ */
+package org.apache.tomcat.lite.io;
+
+import junit.framework.TestCase;
+
+
+public class UEncoderTest extends TestCase {
+    IOWriter enc=new IOWriter(null);
+    UrlEncoding dec = new UrlEncoding();
+    CBuffer cc = CBuffer.newInstance();
+
+    /*
+     *
+     * Test method for 'org.apache.tomcat.util.buf.UEncoder.encodeURL(String)'
+     * TODO: find the relevant rfc and apache tests and add more
+     */
+    public void testEncodeURL() {
+
+        String eurl1=encodeURL("test");
+        assertEquals("test", eurl1);
+
+        eurl1=encodeURL("/test");
+        assertEquals("/test", eurl1);
+
+        // safe ranges
+        eurl1=encodeURL("test$-_.");
+        assertEquals("test$-_.", eurl1);
+
+        eurl1=encodeURL("test$-_.!*'(),");
+        assertEquals("test$-_.!*'(),", eurl1);
+
+        eurl1=encodeURL("//test");
+        assertEquals("//test", eurl1);
+    }
+
+    public String encodeURL(String uri) {
+        cc.recycle();
+        dec.urlEncode(uri, cc, enc);
+        return cc.toString();
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/io/test.properties b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/test.properties
new file mode 100644
index 0000000..19ee5e4
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/io/test.properties
@@ -0,0 +1,33 @@
+RUN=Log,JMXProxy,Socks
+
+Log.(class)=org.apache.tomcat.integration.simple.LogConfig
+Log.debug=org.apache.tomcat.async.AsyncHttpConnector
+Log.debug=Proxy
+
+JMXProxy.(class)=org.apache.tomcat.integration.simple.JMXProxy
+JMXProxy.port=8003
+
+Socks.(class)=org.apache.tomcat.async.callbacks.SocksServer
+Socks.port=2080
+Socks.idleTimeout=0
+
+HttpConnector-TestServer.debug=true
+#HttpConnector-TestServer.debugHttp=true
+HttpConnector-TestServer.clientKeepAlive=true
+HttpConnector-TestServer.serverKeepAlive=true
+HttpConnector-TestServer.maxHttpPoolSize=10
+
+HttpConnector.debug=true
+#HttpConnector.debugHttp=true
+HttpConnector.clientKeepAlive=true
+HttpConnector.serverKeepAlive=true
+HttpConnector.maxHttpPoolSize=10
+
+HttpConnector-Proxy.debug=true
+#HttpConnector-Proxy.debugHttp=true
+HttpConnector-Proxy.clientKeepAlive=true
+HttpConnector-Proxy.serverKeepAlive=true
+HttpConnector-Proxy.maxHttpPoolSize=10
+
+
+#IOConnector.debug=true
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttp5Test.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttp5Test.java
new file mode 100644
index 0000000..d167a5d
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttp5Test.java
@@ -0,0 +1,38 @@
+/*
+ */
+package org.apache.tomcat.lite.load;
+
+import org.apache.tomcat.lite.http.LiveHttp1Test;
+
+import junit.framework.TestSuite;
+
+public class LiveHttp5Test extends LiveHttp1Test {
+
+    /**
+     * Want to run the same tests few times.
+     */
+    public static TestSuite suite() {
+        TestSuite s = new TestSuite();
+        for (int i = 0; i < 5; i++) {
+            s.addTestSuite(LiveHttp1Test.class);
+        }
+        return s;
+    }
+
+    public void test100() throws Exception {
+        for (int i = 0; i < 100; i++) {
+            testSimpleRequest();
+            tearDown();
+            setUp();
+
+            notFound();
+            tearDown();
+            setUp();
+
+            testSimpleRequest();
+            tearDown();
+            setUp();
+        }
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java
new file mode 100644
index 0000000..7849f49
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/LiveHttpThreadedTest.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.load;
+
+
+import java.io.IOException;
+import java.net.HttpURLConnection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.TestMain;
+import org.apache.tomcat.lite.http.HttpClient;
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.http.HttpRequest;
+import org.apache.tomcat.lite.http.HttpChannel.RequestCompleted;
+import org.apache.tomcat.lite.io.BBuffer;
+import org.apache.tomcat.lite.io.SocketConnector;
+
+/*
+  Notes on memory use ( from heap dumps ):
+    - buffers are not yet recycled ( the BBuffers used in channels )
+
+    - each active connection consumes at least 26k - 2 buffers + head buffer
+     ( 8k each )
+     TODO: could 'peak' in the In buffer and move headRecv to HttpChannel
+
+
+    - HttpChannel keeps about 64K ( for the hello world ).
+    -- res is 25k
+    -- req is 32k, BufferedIOReader 16k,
+
+   TODO:
+    - leak in NioThread.active - closed sockets not removed
+    - need to rate-limit and queue requests - OOM
+    - timeouts
+    - seems few responses missing on large async requests (URL works)
+ */
+
+/**
+ * Long running test - async tests are failing since rate control
+ * is not implemented ( too many outstanding requests - OOM ),
+ * it seems there is a bug as well.
+ */
+public class LiveHttpThreadedTest extends TestCase {
+    HttpConnector clientCon = TestMain.shared().getClient();
+    HttpConnector serverCon = TestMain.shared().getTestServer();
+
+    HttpConnector spdyClient =
+        HttpClient.newClient().setCompression(false);
+
+    HttpConnector spdyClientCompress =
+        HttpClient.newClient();
+
+    HttpConnector spdyClientCompressSsl =
+        HttpClient.newClient();
+
+    ThreadRunner tr;
+    static boolean dumpHeap = true;
+
+    AtomicInteger ok = new AtomicInteger();
+    int reqCnt;
+
+    Map<HttpRequest, HttpRequest> active = new HashMap();
+
+    public void tearDown() throws IOException {
+        clientCon.cpool.clear();
+    }
+
+    public void test1000Async() throws Exception {
+//        try {
+            asyncRequest(10, 100, false, false, clientCon, "AsyncHttp");
+//          } finally {
+//          dumpHeap("heapAsync.bin");
+//      }
+
+    }
+
+    public void test10000Async() throws Exception {
+        asyncRequest(20, 500, false, false, clientCon, "AsyncHttp");
+    }
+
+
+    public void test1000AsyncSsl() throws Exception {
+        asyncRequest(20, 50, false, true, clientCon, "AsyncHttpSsl");
+    }
+
+    public void test10000AsyncSsl() throws Exception {
+        asyncRequest(20, 500, false, true, clientCon, "AsyncHttpSsl");
+    }
+
+    public void test1000AsyncSpdy() throws Exception {
+        asyncRequest(10, 100, true, false, spdyClient, "AsyncSpdy");
+    }
+
+    public void test10000AsyncSpdy() throws Exception {
+        asyncRequest(20, 500, true, false, spdyClient, "AsyncSpdy");
+    }
+
+    public void test1000AsyncSpdyComp() throws Exception {
+            asyncRequest(10, 100, true, false, spdyClientCompress, "AsyncSpdyComp");
+    }
+
+    public void test10000AsyncSpdyComp() throws Exception {
+        asyncRequest(20, 500, true, false, spdyClientCompress, "AsyncSpdyComp");
+    }
+
+    public void xtest1000AsyncSpdySsl() throws Exception {
+        asyncRequest(10, 100, true, true, spdyClient, "AsyncSpdySsl");
+    }
+
+    public void xtest1000AsyncSpdyCompSsl() throws Exception {
+        asyncRequest(10, 100, true, true, spdyClientCompress, "AsyncSpdyCompSsl");
+    }
+
+    public void xtest10000AsyncSpdyCompSsl() throws Exception {
+        asyncRequest(20, 500, true, true, spdyClientCompress, "AsyncSpdyCompSsl");
+    }
+
+    Object thrlock = new Object();
+    Object lock = new Object();
+
+    public void asyncRequest(final int thr, int perthr,
+            final boolean spdy, final boolean ssl,
+            final HttpConnector clientCon, String test) throws Exception {
+        clientCon.getConnectionPool().clear();
+        reqCnt = thr * perthr;
+        long t0 = System.currentTimeMillis();
+
+        tr = new ThreadRunner(thr, perthr) {
+            public void makeRequest(int i) throws Exception {
+                HttpRequest cstate = clientCon.request("localhost",
+                        ssl ? 8443 : 8802);
+                synchronized (active) {
+                    active.put(cstate, cstate);
+                }
+                if (spdy) {
+                    // Magic way to force spdy - will be replaced with
+                    // a negotiation.
+                    cstate.setProtocol("SPDY/1.0");
+                }
+                if (ssl) {
+                    cstate.setSecure(true);
+                }
+                cstate.requestURI().set("/hello");
+                cstate.setCompletedCallback(reqCallback);
+                // no body
+                cstate.getBody().close();
+
+                cstate.send();
+
+                while (active.size() >= thr) {
+                    synchronized(thrlock) {
+                        thrlock.wait();
+                    }
+                }
+            }
+        };
+        tr.run();
+        synchronized (lock) {
+            if (ok.get() < reqCnt) {
+                lock.wait(reqCnt * 100);
+            }
+        }
+        long time = (System.currentTimeMillis() - t0);
+
+        System.err.println("====== " + test +
+                " threads: " + thr + ", req: " +
+                reqCnt + ", sendTime" + tr.time +
+                ", time: " + time +
+                ", connections: " + clientCon.getConnectionPool().getSocketCount() +
+                ", avg: " + (time / reqCnt));
+
+        assertEquals(reqCnt, ok.get());
+        assertEquals(0, tr.errors.get());
+    }
+
+    RequestCompleted reqCallback = new RequestCompleted() {
+        @Override
+        public void handle(HttpChannel data, Object extraData)
+        throws IOException {
+            String out = data.getIn().copyAll(null).toString();
+            if (200 != data.getResponse().getStatus()) {
+                System.err.println("Wrong status");
+                tr.errors.incrementAndGet();
+            } else if (!"Hello world".equals(out)) {
+                tr.errors.incrementAndGet();
+                System.err.println("bad result " + out);
+            }
+            synchronized (active) {
+                active.remove(data.getRequest());
+            }
+            synchronized (thrlock) {
+                thrlock.notify();
+            }
+            data.release();
+            int okres = ok.incrementAndGet();
+            if (okres >= reqCnt) {
+                synchronized (lock) {
+                    lock.notify();
+                }
+            }
+        }
+    };
+
+
+
+    public void testURLRequest1000() throws Exception {
+        urlRequest(10, 100, false, "HttpURLConnection");
+    }
+
+    public void xtestURLRequest10000() throws Exception {
+        urlRequest(20, 500, false, "HttpURLConnection");
+
+    }
+
+    // I can't seem to get 1000 requests to all complete...
+    public void xtestURLRequestSsl100() throws Exception {
+        urlRequest(10, 10, true, "HttpURLConnectionSSL");
+    }
+
+    public void xtestURLRequestSsl10000() throws Exception {
+        urlRequest(20, 500, true, "HttpURLConnectionSSL");
+
+    }
+
+    /**
+     * HttpURLConnection client against lite.http server.
+     */
+    public void urlRequest(int thr, int cnt, final boolean ssl, String test)
+            throws Exception {
+        long t0 = System.currentTimeMillis();
+
+
+        try {
+            HttpConnector testServer = TestMain.getTestServer();
+
+            tr = new ThreadRunner(thr, cnt) {
+
+                public void makeRequest(int i) throws Exception {
+                    try {
+                        BBuffer out = BBuffer.allocate();
+                        String url = ssl ? "https://localhost:8443/hello" :
+                            "http://localhost:8802/hello";
+                        HttpURLConnection con =
+                            TestMain.getUrl(url, out);
+                        if (con.getResponseCode() != 200) {
+                            errors.incrementAndGet();
+                        }
+                        if (!"Hello world".equals(out.toString())) {
+                            errors.incrementAndGet();
+                            System.err.println("bad result " + out);
+                        }
+                    } catch(Throwable t) {
+                        t.printStackTrace();
+                        errors.incrementAndGet();
+                    }
+                }
+            };
+            tr.run();
+            assertEquals(0, tr.errors.get());
+            long time = (System.currentTimeMillis() - t0);
+
+            System.err.println("====== " + test + " threads: " + thr + ", req: " +
+                    (thr * cnt) + ", time: " + time + ", avg: " +
+                    (time / (thr * cnt)));
+        } finally {
+            //dumpHeap("heapURLReq.bin");
+        }
+    }
+
+    // TODO: move to a servlet
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java
new file mode 100644
index 0000000..732205e
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/MicroTest.java
@@ -0,0 +1,50 @@
+/*
+ */
+package org.apache.tomcat.lite.load;
+
+import org.apache.tomcat.lite.http.BaseMapper;
+import org.apache.tomcat.lite.http.MappingData;
+import org.apache.tomcat.lite.io.CBuffer;
+
+import junit.framework.TestCase;
+
+public class MicroTest extends TestCase {
+
+    public void testMapper() throws Exception {
+        BaseMapper mapper = new BaseMapper();
+
+        MappingData mappingData = new MappingData();
+        CBuffer host = CBuffer.newInstance();
+        host.set("test1.com");
+
+        CBuffer uri = CBuffer.newInstance();
+        uri.set("/foo/bar/blah/bobou/foo");
+
+        String[] welcomes = new String[2];
+        welcomes[0] = "index.html";
+        welcomes[1] = "foo.html";
+
+        for (int i = 0; i < 100; i++) {
+            String hostN = "test" + i + ".com";
+            mapper.addContext(hostN, "", "context0", new String[0], null, null);
+            mapper.addContext(hostN, "/foo", "context1", new String[0], null, null);
+            mapper.addContext(hostN, "/foo/bar", "context2", welcomes, null, null);
+            mapper.addContext(hostN, "/foo/bar/bla", "context3", new String[0], null, null);
+
+            mapper.addWrapper(hostN, "/foo/bar", "/fo/*", "wrapper0");
+        }
+        int N = 10000;
+        for (int i = 0; i < N; i++) {
+            mappingData.recycle();
+            mapper.map(host, uri, mappingData);
+        }
+
+        long time = System.currentTimeMillis();
+        for (int i = 0; i < N; i++) {
+            mappingData.recycle();
+            mapper.map(host, uri, mappingData);
+        }
+        // TODO: asserts
+        //System.out.println("Elapsed:" + (System.currentTimeMillis() - time));
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java
new file mode 100644
index 0000000..098f5b8
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/load/ThreadRunner.java
@@ -0,0 +1,64 @@
+/*
+ */
+package org.apache.tomcat.lite.load;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class ThreadRunner {
+    int tCount = 10;
+    int rCount = 100;
+    Thread[] threads;
+    int[] ok;
+
+    int sleepTime = 0;
+
+    long time;
+    protected AtomicInteger errors = new AtomicInteger();
+
+    public ThreadRunner(int threads, int count) {
+        tCount = threads;
+        rCount = count;
+        this.threads = new Thread[tCount];
+        ok = new int[tCount];
+    }
+
+    public void run() {
+        long t0 = System.currentTimeMillis();
+        for (int i = 0; i < tCount; i++) {
+          final int j = i;
+          threads[i] = new Thread(new Runnable() {
+            public void run() {
+              makeRequests(j);
+            }
+          });
+          threads[i].start();
+        }
+
+        int res = 0;
+        for (int i = 0; i < tCount; i++) {
+          try {
+            threads[i].join();
+        } catch (InterruptedException e) {
+            e.printStackTrace();
+        }
+          res += ok[i];
+        }
+        long t1 = System.currentTimeMillis();
+        time = t1 - t0;
+    }
+
+    public void makeRequests(int cnt) {
+        for (int i = 0; i < rCount ; i++) {
+            try {
+              //System.err.println("MakeReq " + t + " " + i);
+              makeRequest(cnt);
+            } catch (Exception e) {
+              e.printStackTrace();
+            }
+          }
+    }
+
+    public void makeRequest(int i) throws Exception {
+
+    }
+}
\ No newline at end of file
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/LiveProxyHttp1Test.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/LiveProxyHttp1Test.java
new file mode 100644
index 0000000..d04d624
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/LiveProxyHttp1Test.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.proxy;
+
+
+import java.io.IOException;
+
+import org.apache.tomcat.lite.http.LiveHttp1Test;
+
+
+public class LiveProxyHttp1Test extends LiveHttp1Test {
+    public void setUp() throws IOException {
+        // All tests in super, but with client pointing to
+        // the proxy server, which in turn hits the real server.
+        clientPort = 8903;
+        super.setUp();
+  }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java
new file mode 100644
index 0000000..9d3181c
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/ProxyTest.java
@@ -0,0 +1,120 @@
+/*
+n * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.lite.proxy;
+
+import java.io.IOException;
+
+import org.apache.tomcat.lite.TestMain;
+
+import junit.framework.TestCase;
+
+
+public class ProxyTest extends TestCase {
+
+  String resStr;
+
+  public void setUp() throws Exception {
+      TestMain.getTestServer();
+  }
+
+  public void tearDown() throws IOException {
+  }
+
+  public void xtestRequestSlowChunked() throws Exception {
+      resStr =
+          TestMain.get("http://localhost:8903/sleep/1c").toString();
+      assertEquals("sleep 1csleep 1c", resStr);
+  }
+
+  public void testSingleRequest() throws Exception {
+      String resStr =
+          TestMain.get("http://localhost:8903/hello").toString();
+      assertEquals("Hello world", resStr);
+  }
+
+
+  public void test2Requests() throws Exception {
+      String resStr =
+          TestMain.get("http://localhost:8903/hello").toString();
+      assertEquals("Hello world", resStr);
+      resStr =
+          TestMain.get("http://localhost:8903/hello?a=b").toString();
+      assertEquals("Hello world", resStr);
+  }
+
+  public void testRequestSimple() throws Exception {
+      resStr =
+          TestMain.get("http://localhost:8903/hello").toString();
+      assertEquals("Hello world", resStr);
+      resStr =
+          TestMain.get("http://localhost:8903/hello").toString();
+      assertEquals("Hello world", resStr);
+      resStr =
+          TestMain.get("http://localhost:8903/hello").toString();
+      assertEquals(resStr, "Hello world");
+
+  }
+
+  public void testExtAdapter() throws Exception {
+      String res =
+              TestMain.get("http://www.apache.org/").toString();
+      assertTrue(res.indexOf("Apache") > 0);
+
+      Thread.currentThread().sleep(100);
+      // second time - are we reusing ?
+      res =
+          TestMain.get("http://www.apache.org/").toString();
+
+      assertTrue(res.indexOf("Apache") > 0);
+
+  }
+
+  public void testStaticAdapter() throws Exception {
+
+      assertEquals("Hello world",
+          TestMain.get("http://localhost:8802/hello").toString());
+      assertEquals("Hello world2",
+          TestMain.get("http://localhost:8802/2nd").toString());
+
+    }
+
+  public void testRequestParams() throws Exception {
+      // qry string
+      String resStr =
+          TestMain.get("http://localhost:8903/echo/foo?q=a&b")
+          .toString();
+      assertTrue(resStr, resStr.indexOf("foo?q=a&b") > 0);
+  }
+
+
+  public void testRequestChunked() throws Exception {
+      // Chunked encoding
+      String resStr =
+          TestMain.get("http://localhost:8903/chunked/test")
+          .toString();
+      assertEquals(4, resStr.length());
+      assertTrue(resStr.indexOf("AAA") >= 0);
+  }
+
+
+  public void testRequestSlow() throws Exception {
+      // Slow
+      String resStr =
+          TestMain.get("http://localhost:8903/sleep/1").toString();
+      assertEquals("sleep 1sleep 1", resStr.toString());
+  }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/SmallProxyTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/SmallProxyTest.java
new file mode 100644
index 0000000..bc8bac9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/proxy/SmallProxyTest.java
@@ -0,0 +1,109 @@
+/*
+ */
+package org.apache.tomcat.lite.proxy;
+
+import java.io.IOException;
+
+import junit.framework.TestCase;
+
+import org.apache.tomcat.lite.http.HttpChannel;
+import org.apache.tomcat.lite.http.HttpConnector;
+import org.apache.tomcat.lite.http.HttpConnector.HttpConnection;
+import org.apache.tomcat.lite.io.MemoryIOConnector;
+import org.apache.tomcat.lite.io.MemoryIOConnector.MemoryIOChannel;
+
+public class SmallProxyTest extends TestCase {
+
+    MemoryIOConnector memoryServerConnector =
+        new MemoryIOConnector();
+
+    MemoryIOConnector memoryClientConnector =
+        new MemoryIOConnector().withServer(memoryServerConnector);
+
+
+    HttpConnector httpCon = new HttpConnector(memoryServerConnector) {
+        @Override
+        public HttpChannel get(CharSequence target) throws IOException {
+            throw new IOException();
+        }
+        public HttpChannel getServer() {
+            lastServer = new HttpChannel().serverMode(true);
+            lastServer.setConnector(this);
+            //lastServer.withIOConnector(memoryServerConnector);
+            return lastServer;
+        }
+    };
+
+    HttpConnector httpClient = new HttpConnector(memoryClientConnector) {
+        @Override
+        public HttpChannel get(CharSequence target) throws IOException {
+            lastClient = new HttpChannel();
+            lastClient.setConnector(this);
+            return lastClient;
+        }
+        public HttpChannel get(String host, int port) throws IOException {
+            lastClient = new HttpChannel();
+            lastClient.setConnector(this);
+            return lastClient;
+        }
+        public HttpChannel getServer() {
+            throw new RuntimeException();
+        }
+    };
+
+    HttpChannel lastServer;
+    HttpChannel lastClient;
+
+    boolean hasBody = false;
+    boolean bodyDone = false;
+    boolean bodySentDone = false;
+    boolean headersDone = false;
+    boolean allDone = false;
+
+
+    //MemoryIOChannel clientNet = new MemoryIOChannel();
+
+    MemoryIOConnector.MemoryIOChannel net = new MemoryIOChannel();
+    HttpChannel http;
+
+    HttpConnection serverConnection;
+
+    public void setUp() throws IOException {
+        http = httpCon.getServer();
+        serverConnection = httpCon.handleAccepted(net);
+    }
+
+    /**
+     * More complicated test..
+     * @throws IOException
+     */
+    public void testProxy() throws IOException {
+        httpCon.setHttpService(new HttpProxyService()
+            .withSelector(memoryClientConnector)
+            .withHttpClient(httpClient));
+
+        net.getIn().append("GET http://www.apache.org/ HTTP/1.0\n" +
+                "Connection: Close\n\n");
+        net.getIn().close();
+
+        // lastClient.rawSendBuffers has the request sent by proxy
+        lastClient.getNet().getIn()
+            .append("HTTP/1.0 200 OK\n\nHi\n");
+        lastClient.getNet().getIn()
+            .append("world\n");
+
+        // TODO: check what the proxy sent
+        // lastClient.getOut();
+
+        // will also trigger 'release' - both sides are closed.
+        lastClient.getNet().getIn().close();
+
+        // wait response...
+        // http.sendBody.close();
+        String res = net.out.toString();
+        assertTrue(res.indexOf("Hi\nworld\n") > 0);
+        assertTrue(res.indexOf("HTTP/1.0 200 OK") == 0);
+        assertTrue(res.indexOf("tomcatproxy") > 0);
+
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java b/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java
new file mode 100644
index 0000000..ba606f0
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/lite/util/UEncoderTest.java
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.lite.util;
+
+import junit.framework.TestCase;
+
+public class UEncoderTest extends TestCase {
+    URLEncoder enc=new URLEncoder();
+
+    /*
+     *
+     * Test method for 'org.apache.tomcat.util.buf.UEncoder.encodeURL(String)'
+     * TODO: find the relevant rfc and apache tests and add more
+     */
+    public void testEncodeURL() {
+
+        String eurl1=enc.encodeURL("test");
+        assertEquals("test", eurl1);
+
+        eurl1=enc.encodeURL("/test");
+        assertEquals("/test", eurl1);
+
+        // safe ranges
+        eurl1=enc.encodeURL("test$-_.");
+        assertEquals("test$-_.", eurl1);
+
+        eurl1=enc.encodeURL("test$-_.!*'(),");
+        assertEquals("test$-_.!*'(),", eurl1);
+
+        eurl1=enc.encodeURL("//test");
+        assertEquals("//test", eurl1);
+
+
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/AntProperties.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/AntProperties.java
new file mode 100644
index 0000000..7e1b03b
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/AntProperties.java
@@ -0,0 +1,75 @@
+/*
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.util.Hashtable;
+
+/**
+ * Extracted from IntrospectionHelper - a simple utility class to
+ * do ant style ${property} replacements on a string, using a map
+ * holding properties. Also allows a hook for dynamic, on-demand
+ * properties.
+ *
+ * @author Costin Manolache
+ */
+public class AntProperties {
+    public static interface PropertySource {
+        public String getProperty(String key);
+    }
+
+    /**
+     * Replace ${NAME} with the property value
+     */
+    public static String replaceProperties(String value,
+            Hashtable<Object,Object> staticProp, PropertySource dynamicProp[]) {
+        if (value.indexOf("$") < 0) {
+            return value;
+        }
+        StringBuffer sb = new StringBuffer();
+        int prev = 0;
+        // assert value!=nil
+        int pos;
+        while ((pos = value.indexOf("$", prev)) >= 0) {
+            if (pos > 0) {
+                sb.append(value.substring(prev, pos));
+            }
+            if (pos == (value.length() - 1)) {
+                sb.append('$');
+                prev = pos + 1;
+            } else if (value.charAt(pos + 1) != '{') {
+                sb.append('$');
+                prev = pos + 1; // XXX
+            } else {
+                int endName = value.indexOf('}', pos);
+                if (endName < 0) {
+                    sb.append(value.substring(pos));
+                    prev = value.length();
+                    continue;
+                }
+                String n = value.substring(pos + 2, endName);
+                String v = null;
+                if (staticProp != null) {
+                    v = (String) staticProp.get(n);
+                }
+                if (v == null && dynamicProp != null) {
+                    for (int i = 0; i < dynamicProp.length; i++) {
+                        v = dynamicProp[i].getProperty(n);
+                        if (v != null) {
+                            break;
+                        }
+                    }
+                }
+                if (v == null)
+                    v = "${" + n + "}";
+
+                sb.append(v);
+                prev = endName + 1;
+            }
+        }
+        if (prev < value.length())
+            sb.append(value.substring(prev));
+        return sb.toString();
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java
new file mode 100644
index 0000000..0c7e64c
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/CookieController.java
@@ -0,0 +1,636 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.test.watchdog;
+
+import java.io.BufferedReader;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileReader;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.Enumeration;
+import java.util.Hashtable;
+import java.util.Properties;
+import java.util.Vector;
+
+
+/**
+ * Represents a collection of Cookie instances.
+ * <p>
+ * Fires events when the cookies have been changed internally. Deals
+ * with management of cookies in terms of saving and loading them,
+ * and disabling them.
+ *
+ * @author	Ramesh.Mandava
+ */
+public class CookieController {
+
+//    private VetoableChangeSupport vceListeners;
+
+    private static Hashtable cookieJar = new Hashtable();
+
+    /* public no arg constructor for bean */
+    public CookieController() {
+    }
+
+/////////////////////////////////////////////////////////////
+ /**
+     * Records any cookies which have been sent as part of an HTTP response.
+     * The connection parameter must be already have been opened, so that
+     * the response headers are available.  It's ok to pass a non-HTTP
+     * URL connection, or one which does not have any set-cookie headers.
+     */
+    public void recordAnyCookies(Vector rcvVectorOfCookies , URL url ) {
+
+	if ((rcvVectorOfCookies == null) || ( rcvVectorOfCookies.size()== 0) ) {
+	    // no headers here
+	    return;
+	}	
+	try {
+	/*
+        Properties properties = new Properties();
+	FileInputStream fin = new FileInputStream("ServerAutoRun.properties");
+	properties.load(fin);
+
+	String cookiepolicy = properties.getProperty("cookie.acceptpolicy");
+	if (cookiepolicy == null || cookiepolicy.equals("none")) {
+	    return;
+	}
+	*/
+
+
+	for (int hi = 0; hi<rcvVectorOfCookies.size(); hi++) {
+
+		String cookieValue = (String)rcvVectorOfCookies.elementAt(hi) ;
+		recordCookie(url, cookieValue); // What to do here
+	    }
+
+	}
+	catch( Exception e )
+	{
+		System.out.println("exception : " + e );
+	}
+    }
+
+
+    /**
+     * Create a cookie from the cookie, and use the HttpURLConnection to
+     * fill in unspecified values in the cookie with defaults.
+     */
+    public void recordCookie(URL url,
+				     String cookieValue) {
+	HttpCookie cookie = new HttpCookie(url, cookieValue);
+	
+	// First, check to make sure the cookie's domain matches the
+	// server's, and has the required number of '.'s
+	String twodot[]=
+	    {"com", "edu", "net", "org", "gov", "mil", "int"};
+	String domain = cookie.getDomain();
+	if( domain == null )
+	    return;
+	int index = domain.indexOf(':');
+	if (index != -1) {
+	    int portCookie;
+	    try {
+		portCookie = (Integer.valueOf(domain.substring(index+1,domain.length()))).intValue();
+	    } catch (Exception e) {
+		return;
+	    }
+	    portCookie = ( portCookie == -1 ) ? 80 : portCookie;
+	    domain=domain.substring(0,index);
+	}
+	domain.toLowerCase();
+	
+	String host = url.getHost();
+	host.toLowerCase();
+
+	boolean domainOK = host.equals(domain);
+	if( !domainOK && host.endsWith( domain ) ) {
+	    int dotsNeeded = 2;
+	    for( int i = 0; i < twodot.length; i++ ) {
+		if( domain.endsWith( twodot[i] ) ) {
+		    dotsNeeded = 1;
+		}
+	    }
+
+	    int lastChar = domain.length();
+	    for( ; lastChar > 0 && dotsNeeded > 0; dotsNeeded-- ) {
+		lastChar = domain.lastIndexOf( '.', lastChar-1 );
+	    }
+
+	    if( lastChar > 0 )
+		domainOK = true;
+	}
+
+	if( domainOK ) {
+	    recordCookie(cookie);
+
+	}
+    }
+
+
+    /**
+     * Record the cookie in the in-memory container of cookies.  If there
+     * is already a cookie which is in the exact same domain with the
+     * exact same
+     */
+    public void recordCookie(HttpCookie cookie) {
+	if (!checkIfCookieOK(cookie)) {
+	    return;
+	}
+	synchronized (cookieJar) {
+	
+	    String domain = cookie.getDomain().toLowerCase();
+
+	    Vector cookieList = (Vector)cookieJar.get(domain);
+	    if (cookieList == null) {
+		cookieList = new Vector();
+	    }
+
+	    addOrReplaceCookie(cookieList, cookie);
+	    cookieJar.put(domain, cookieList);
+	
+	}
+
+    }
+
+    public boolean checkIfCookieOK(HttpCookie cookie) {
+	return true;
+    }
+
+    /**
+     * Scans the vector of cookies looking for an exact match with the
+     * given cookie.  Replaces it if there is one, otherwise adds
+     * one at the end.  The vector is presumed to have cookies which all
+     * have the same domain, so the domain of the cookie is not checked.
+     * <p>
+     * <p>
+     * If this is called, it is assumed that the cookie jar is exclusively
+     * held by the current thread.
+     *
+     */
+    private void addOrReplaceCookie(Vector cookies,
+				       HttpCookie cookie) {
+	int numCookies = cookies.size();
+
+	String path = cookie.getPath();
+	String name = cookie.getName();
+	HttpCookie replaced = null;
+	int replacedIndex = -1;
+
+	for (int i = 0; i < numCookies; i++) {
+	    HttpCookie existingCookie = (HttpCookie)cookies.elementAt(i);
+	
+	    String existingPath = existingCookie.getPath();
+	    if (path.equals(existingPath)) {
+		String existingName = existingCookie.getName();
+		if (name.equals(existingName)) {
+		    // need to replace this one!
+		    replaced = existingCookie;
+		    replacedIndex = i;
+		    break;
+		}
+	    }
+	}
+	
+	
+	// Do the replace - if cookie has already expired, remove
+	// the replaced cookie.
+	if (replaced != null) {
+	    if (cookie.isSaveableInMemory()) {
+		cookies.setElementAt(cookie, replacedIndex);
+		//System.out.println("REPLACED existing cookie with " + cookie);
+	    } else {
+		cookies.removeElementAt(replacedIndex);
+		//System.out.println("Removed cookie b/c or expr " + cookie);
+	    }
+
+	} else { // only save the cookie in memory if it is non persistent
+          	 // or not expired.
+	    if (cookie.isSaveableInMemory()) {
+		cookies.addElement(cookie);
+		//System.out.println("RECORDED new cookie " + cookie);
+	    }
+
+	}
+
+    }
+
+    public String applyRelevantCookies(URL url ) {
+
+       try {	
+		/*
+		Properties properties = new Properties();
+		FileInputStream fin = new FileInputStream("ServerAutoRun.properties");
+		properties.load(fin);
+		// check current accept policy instead enableCookies
+		String cookiepolicy = properties.getProperty("cookie.acceptpolicy");
+		if (cookiepolicy == null || cookiepolicy.equals("none")) {
+		    return null;
+		}
+
+		*/
+
+		return applyCookiesForHost(url);
+
+	}
+	catch ( Exception e )
+	{
+		System.out.println("Exception : " +e );
+		return null;
+	}
+
+
+    }
+
+
+   /**
+     * Host may be a FQDN, or a partial domain name starting with a dot.
+     * Adds any cookies which match the host and path to the
+     * cookie set on the URL connection.
+     */
+    private String applyCookiesForHost(URL url ){
+	String cookieString = null;
+	Vector cookieVector = getAllRelevantCookies(url);
+	
+	if (cookieVector != null) {
+
+	    for (Enumeration e = cookieVector.elements(); e.hasMoreElements();) {
+		HttpCookie cookie = (HttpCookie)e.nextElement();
+		if( cookieString == null ) {
+		    cookieString = cookie.getNameValue();
+		} else {
+		    cookieString = cookieString + "; " + cookie.getNameValue();
+		}
+	    }
+	
+	 /*
+
+	    if( cookieString != null ) {
+		httpConn.setRequestProperty("Cookie", cookieString);
+	
+//		System.out.println("Returned cookie string: " + cookieString + " for HOST = " + host);
+	     }
+
+	  */
+
+
+	}
+//		System.out.println("Returned cookie string: " + cookieString + " for HOST = " + host);
+	return cookieString;
+	
+    }
+
+    private Vector getAllRelevantCookies(URL url) {
+	String host = url.getHost();
+	Vector cookieVector = getSubsetRelevantCookies(host, url);
+
+	Vector tempVector;
+	int index;
+
+	while ((index = host.indexOf('.', 1)) >= 0) {
+	    // trim off everything up to, and including the dot.
+	    host = host.substring(index+1);
+	
+            // add onto cookieVector
+	    tempVector = getSubsetRelevantCookies(host,url);
+	    if (tempVector != null ) {
+		for (Enumeration e = tempVector.elements(); e.hasMoreElements(); ) {
+		    if (cookieVector == null) {
+			cookieVector = new Vector(2);
+		    }
+
+		    cookieVector.addElement(e.nextElement());
+
+		}
+	    }
+	}
+	return cookieVector;
+    }
+
+    private Vector getSubsetRelevantCookies(String host, URL url) {
+
+	Vector cookieList = (Vector)cookieJar.get(host);
+	
+//	System.out.println("getRelevantCookies() .. for host, url " + host +", "+url);
+	Vector cookiePortList = (Vector)cookieJar.get(host+":"+((url.getPort() == -1) ? 80 : url.getPort()));
+	if (cookiePortList != null) {
+	    if (cookieList == null) {
+		cookieList = new Vector(10);
+	    }
+	    Enumeration cookies = cookiePortList.elements();
+	    while (cookies.hasMoreElements()) {
+		cookieList.addElement(cookies.nextElement());
+	    }
+	}
+	
+	
+	if (cookieList == null) {
+	    return null;
+	}
+
+	String path = url.getFile();
+//	System.out.println("        path is " + path + "; protocol = " + url.getProtocol());
+
+
+	int queryInd = path.indexOf('?');
+	if (queryInd > 0) {
+	    // strip off the part following the ?
+	    path = path.substring(0, queryInd);
+	}
+
+	Enumeration cookies = cookieList.elements();
+	Vector cookiesToSend = new Vector(10);
+
+	while (cookies.hasMoreElements()) {
+	    HttpCookie cookie = (HttpCookie)cookies.nextElement();
+	
+	    String cookiePath = cookie.getPath();
+
+	    if (path.startsWith(cookiePath)) {
+		// larrylf: Actually, my documentation (from Netscape)
+		// says that /foo should
+		// match /foobar and /foo/bar.  Yuck!!!
+
+		if (!cookie.hasExpired()) {
+		    cookiesToSend.addElement(cookie);
+		}
+
+/*
+   We're keeping this piece of commented out code around just in
+   case we decide to put it back.  the spec does specify the above,
+   but it is so disgusting!
+
+		int cookiePathLen = cookiePath.length();
+
+		// verify that /foo does not match /foobar by mistake
+		if ((path.length() == cookiePathLen)
+		    || (path.length() > cookiePathLen &&
+			path.charAt(cookiePathLen) == '/')) {
+		
+		    // We have a matching cookie!
+
+		    if (!cookie.hasExpired()) {
+			cookiesToSend.addElement(cookie);
+		    }
+		}
+*/
+	    }
+	}
+
+	// Now, sort the cookies in most to least specific order
+	// Yes, its the deaded bubblesort!! Wah Ha-ha-ha-ha....
+	// (it should be a small vector, so perf is not an issue...)
+	if( cookiesToSend.size() > 1 ) {
+	    for( int i = 0; i < cookiesToSend.size()-1; i++ ) {
+		HttpCookie headC = (HttpCookie)cookiesToSend.elementAt(i);
+		String head = headC.getPath();
+		// This little excercise is a cheap way to get
+		// '/foo' to read more specfic then '/'
+		if( !head.endsWith("/") ) {
+		    head = head + "/";
+		}
+		for( int j = i+1; j < cookiesToSend.size(); j++ ) {
+		    HttpCookie scanC = (HttpCookie)cookiesToSend.elementAt(j);
+		    String scan = scanC.getPath();
+		    if( !scan.endsWith("/") ) {
+			scan = scan + "/";
+		    }
+
+		    int headCount = 0;
+		    int index = -1;
+		    while( (index=head.indexOf('/', index+1)) != -1 ) {
+			headCount++;
+		    }
+		    index = -1;
+
+		    int scanCount = 0;
+		    while( (index=scan.indexOf('/', index+1)) != -1 ) {
+			scanCount++;
+		    }
+
+		    if( scanCount > headCount ) {
+			cookiesToSend.setElementAt(headC, j);
+			cookiesToSend.setElementAt(scanC, i);
+			headC = scanC;
+			head = scan;
+		    }
+		}
+	    }
+	}
+
+
+    return cookiesToSend;
+
+    }
+
+    /*
+     * Writes cookies out to PrintWriter if they are persistent
+     * (i.e. have a expr date)
+     * and haven't expired. Will remove cookies that have expired as well
+     */
+    private void saveCookiesToStream(PrintWriter pw) {
+
+	Enumeration cookieLists = cookieJar.elements();
+		
+	while (cookieLists.hasMoreElements()) {
+	    Vector cookieList = (Vector)cookieLists.nextElement();
+
+	    Enumeration cookies = cookieList.elements();
+
+	    while (cookies.hasMoreElements()) {
+		HttpCookie cookie = (HttpCookie)cookies.nextElement();
+		
+		if (cookie.getExpirationDate() != null) {
+		    if (cookie.isSaveable()) {
+			pw.println(cookie);
+		    } else { // the cookie must have expired,
+			//remove from Vector cookieList
+			cookieList.removeElement(cookie);
+		    }
+		
+		}
+	    }
+	}
+        // Must print something to the printwriter in the case that
+	// the cookieJar has been cleared - otherwise the old cookie
+	// file will continue to exist.
+	pw.print("");
+    }
+/////////////////////////////////////////////////////////////
+    /* adds cookieList to the existing cookie jar*/
+    public void addToCookieJar(HttpCookie[] cookieList) {
+
+	if (cookieList != null) {
+	    for (int i = 0; i < cookieList.length; i++) {
+		
+		recordCookie(cookieList[i]);
+	    }
+	}
+
+    }
+
+    /*adds one cookie to the Cookie Jar */
+    public void addToCookieJar(String cookieString, URL docURL) {
+	recordCookie(new HttpCookie(docURL, cookieString));
+    }
+
+    /* loads the cookies from the given filename */
+    public void loadCookieJarFromFile(String cookieFileName) {
+	try {
+	    FileReader fr = new FileReader(cookieFileName);
+	
+	    BufferedReader in = new BufferedReader(fr);
+
+	    try {
+		String cookieString;
+		while ((cookieString = in.readLine()) != null) {
+		    HttpCookie cookie = new HttpCookie(cookieString);
+		    // Record the cookie, without notification.  We don't
+		    // do a notification for cookies that are read at
+		    // program start-up.
+		    recordCookie(cookie);
+		}
+	    } finally {
+		in.close();
+	    }
+
+	
+	} catch (IOException e) {
+	    // do nothing; it's not an error not to have persistent cookies
+	}
+
+    }
+
+    /* saves the cookies to the given file specified by fname */
+    public void saveCookieJarToFile(String cookieFileName) {
+	try {
+	    FileWriter fw = new FileWriter(cookieFileName);
+	    PrintWriter pw = new PrintWriter(fw, false);
+
+	    try {
+		saveCookiesToStream(pw);
+	    } finally {
+		pw.close();
+	    }
+
+	} catch (IOException e) {
+	    // REMIND: I18N
+	    System.err.println("Saving cookies failed " + e.getMessage());
+	}
+    }
+
+    /**
+     * Return an array with all of the cookies represented by this
+     * jar.  This is useful when the bean is shutting down, and the client
+     * wants to make the cookie jar persist.
+     */
+    public HttpCookie[] getAllCookies() {
+
+	Vector result = new Vector();
+	Hashtable jar;
+	jar = (Hashtable) cookieJar.clone();
+	
+	synchronized (jar) {
+	
+	    for (Enumeration e = jar.elements(); e.hasMoreElements() ;) {
+		Vector v = (Vector) e.nextElement();
+		for (int i = 0; i < v.size(); i++) {
+		    HttpCookie hc = (HttpCookie) v.elementAt(i);
+		    result.addElement(hc);
+		
+		}
+		
+	    }
+	}
+
+	HttpCookie[] resultA = new HttpCookie[result.size()];
+	for (int i = 0; i < result.size(); i++) {
+	    resultA[i] = (HttpCookie) result.elementAt(i);
+	}
+	return resultA;
+    }
+
+    /* Gets all cookies that applies for the URL */
+    public HttpCookie[] getCookiesForURL(URL url) {
+
+	Vector cookieVector = getAllRelevantCookies(url);
+
+	if (cookieVector == null) {
+	    return null;
+	}
+
+	int i = 0;
+	HttpCookie[] cookieArr = new HttpCookie[cookieVector.size()];
+
+	for (Enumeration e = cookieVector.elements(); e.hasMoreElements(); ) {
+
+	    cookieArr[i++] = (HttpCookie)e.nextElement();
+//	    System.out.println("cookieArr["+(i-1)+"] = " +cookieArr[i-1].toString());
+	}
+	
+	return cookieArr;
+    }
+
+    /* this will set the property of enableCookies to isDisabled */
+    public void setCookieDisable(boolean isDisabled) {
+
+	// Pending visit back this again
+	try {
+	Properties properties = new Properties();
+	properties.load(new FileInputStream("ServerAutoRun.properties") );
+	
+	
+	properties.put("enableCookies", isDisabled ? "false" : "true");
+	properties.store(new FileOutputStream("ServerAutoRun.properties"),"comments");
+	}
+	catch ( Exception e )
+	{
+		System.out.println("Exception : " + e );
+	}
+    }
+
+    public void discardAllCookies() {
+	cookieJar.clear();
+	
+    }
+
+    /*
+     * purges any expired cookies in the Cookie hashtable.
+     */
+    public void purgeExpiredCookies() {
+	Enumeration cookieLists = cookieJar.elements();
+		
+	while (cookieLists.hasMoreElements()) {
+	    Vector cookieList = (Vector)cookieLists.nextElement();
+
+	    Enumeration cookies = cookieList.elements();
+
+	    while (cookies.hasMoreElements()) {
+		HttpCookie cookie = (HttpCookie)cookies.nextElement();
+		
+		if (cookie.hasExpired()) {
+		    cookieList.removeElement(cookie);
+		}
+	    }
+	}
+
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/DynamicObject.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/DynamicObject.java
new file mode 100644
index 0000000..c01b5be
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/DynamicObject.java
@@ -0,0 +1,385 @@
+/*
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.lang.reflect.AccessibleObject;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+/**
+ * Refactoring of IntrospectionUtils and modeler dynamic bean.
+ *
+ * Unlike IntrospectionUtils, the method informations can be cached.
+ * Also I hope this class will be simpler to use.
+ * There is no static cache.
+ *
+ * @author Costin Manolache
+ */
+public class DynamicObject {
+    // Based on MbeansDescriptorsIntrospectionSource
+
+    private static Logger log = Logger.getLogger(DynamicObject.class.getName());
+
+    private static Class<?> NO_PARAMS[] = new Class[0];
+
+
+    private static String strArray[] = new String[0];
+
+    private static Class<?>[] supportedTypes = new Class[] { Boolean.class,
+            Boolean.TYPE, Byte.class, Byte.TYPE, Character.class,
+            Character.TYPE, Short.class, Short.TYPE, Integer.class,
+            Integer.TYPE, Long.class, Long.TYPE, Float.class, Float.TYPE,
+            Double.class, Double.TYPE, String.class, strArray.getClass(),
+            BigDecimal.class, BigInteger.class, AtomicInteger.class,
+            AtomicLong.class, java.io.File.class, };
+
+
+    private Class realClass;
+
+    // Method or Field
+    private Map<String, AccessibleObject> getAttMap;
+
+    public DynamicObject(Class beanClass) {
+        this.realClass = beanClass;
+        initCache();
+    }
+
+    private void initCache() {
+        Method methods[] = null;
+
+        getAttMap = new HashMap<String, AccessibleObject>();
+
+        methods = realClass.getMethods();
+        for (int j = 0; j < methods.length; ++j) {
+            if (ignorable(methods[j])) {
+                continue;
+            }
+            String name = methods[j].getName();
+
+            Class<?> params[] = methods[j].getParameterTypes();
+
+            if (name.startsWith("get") && params.length == 0) {
+                Class<?> ret = methods[j].getReturnType();
+                if (!supportedType(ret)) {
+                    if (log.isLoggable(Level.FINE))
+                        log.fine("Unsupported type " + methods[j]);
+                    continue;
+                }
+                name = unCapitalize(name.substring(3));
+
+                getAttMap.put(name, methods[j]);
+            } else if (name.startsWith("is") && params.length == 0) {
+                Class<?> ret = methods[j].getReturnType();
+                if (Boolean.TYPE != ret) {
+                    if (log.isLoggable(Level.FINE))
+                        log.fine("Unsupported type " + methods[j] + " " + ret);
+                    continue;
+                }
+                name = unCapitalize(name.substring(2));
+
+                getAttMap.put(name, methods[j]);
+            }
+        }
+        // non-private AtomicInteger and AtomicLong - stats
+        Field fields[] = realClass.getFields();
+        for (int j = 0; j < fields.length; ++j) {
+            if (fields[j].getType() == AtomicInteger.class) {
+                getAttMap.put(fields[j].getName(), fields[j]);
+            }
+        }
+
+    }
+
+    public List<String> attributeNames() {
+        return new ArrayList<String>(getAttMap.keySet());
+    }
+
+
+    public Object invoke(Object proxy, String method) throws Exception {
+        Method executeM = null;
+        Class<?> c = proxy.getClass();
+        executeM = c.getMethod(method, NO_PARAMS);
+        if (executeM == null) {
+            throw new RuntimeException("No execute in " + proxy.getClass());
+        }
+        return executeM.invoke(proxy, (Object[]) null);
+    }
+
+    // TODO
+//    public Object invoke(String method, Object[] params) {
+//        return null;
+//    }
+
+    public Object getAttribute(Object o, String att) {
+        AccessibleObject m = getAttMap.get(att);
+        if (m instanceof Method) {
+            try {
+                return ((Method) m).invoke(o);
+            } catch (Throwable e) {
+                log.log(Level.INFO, "Error getting attribute " + realClass + " "
+                        + att, e);
+                return null;
+            }
+        } if (m instanceof Field) {
+            if (((Field) m).getType() == AtomicInteger.class) {
+                try {
+                    Object value = ((Field) m).get(o);
+                    return ((AtomicInteger) value).get();
+                } catch (Throwable e) {
+                    return null;
+                }
+            } else {
+                return null;
+            }
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Set an object-type attribute.
+     *
+     * Use setProperty to use a string value and convert it to the
+     * specific (primitive) type.
+     */
+    public boolean setAttribute(Object proxy, String name, Object value) {
+        // TODO: use the cache...
+        String methodName = "set" + capitalize(name);
+        Method[] methods = proxy.getClass().getMethods();
+        for (Method m : methods) {
+            Class<?>[] paramT = m.getParameterTypes();
+            if (methodName.equals(m.getName())
+                    && paramT.length == 1
+                    && (value == null || paramT[0].isAssignableFrom(value
+                            .getClass()))) {
+                try {
+                    m.invoke(proxy, value);
+                    return true;
+                } catch (IllegalArgumentException e) {
+                    log.severe("Error setting: " + name + " "
+                            + proxy.getClass().getName() + " " + e);
+                } catch (IllegalAccessException e) {
+                    log.severe("Error setting: " + name + " "
+                            + proxy.getClass().getName() + " " + e);
+                } catch (InvocationTargetException e) {
+                    log.severe("Error setting: " + name + " "
+                            + proxy.getClass().getName() + " " + e);
+                }
+            }
+        }
+        return false;
+    }
+
+    public boolean setProperty(Object proxy, String name, String value) {
+        // TODO: use the cache...
+        String setter = "set" + capitalize(name);
+
+        try {
+            Method methods[] = proxy.getClass().getMethods();
+
+            Method setPropertyMethod = null;
+
+            // First, the ideal case - a setFoo( String ) method
+            for (int i = 0; i < methods.length; i++) {
+                if (ignorable(methods[i])) {
+                    continue;
+                }
+                Class<?> paramT[] = methods[i].getParameterTypes();
+                if (setter.equals(methods[i].getName()) && paramT.length == 1) {
+                    if ("java.lang.String".equals(paramT[0].getName())) {
+                        methods[i].invoke(proxy, new Object[] { value });
+                        return true;
+                    } else {
+                        // match - find the type and invoke it
+                        Class<?> paramType = methods[i].getParameterTypes()[0];
+                        Object params[] = new Object[1];
+                        params[0] = convert(value, paramType);
+                        if (params[0] != null) {
+                            methods[i].invoke(proxy, params);
+                            return true;
+                        }
+                    }
+                }
+                // save "setProperty" for later
+                if ("setProperty".equals(methods[i].getName()) &&
+                        paramT.length == 2 &&
+                        paramT[0] == String.class &&
+                        paramT[1] == String.class) {
+                    setPropertyMethod = methods[i];
+                }
+            }
+
+            try {
+                Field field = proxy.getClass().getField(name);
+                if (field != null) {
+                    Object conv = convert(value, field.getType());
+                    if (conv != null) {
+                        field.set(proxy, conv);
+                        return true;
+                    }
+                }
+            } catch (NoSuchFieldException e) {
+                // ignore
+            }
+
+            // Ok, no setXXX found, try a setProperty("name", "value")
+            if (setPropertyMethod != null) {
+                Object params[] = new Object[2];
+                params[0] = name;
+                params[1] = value;
+                setPropertyMethod.invoke(proxy, params);
+                return true;
+            }
+
+        } catch (Throwable ex2) {
+            log.log(Level.WARNING, "IAE " + proxy + " " + name + " " + value,
+                    ex2);
+        }
+        return false;
+    }
+
+    // ----------- Helpers ------------------
+
+    static Object convert(String object, Class<?> paramType) {
+        Object result = null;
+        if ("java.lang.String".equals(paramType.getName())) {
+            result = object;
+        } else if ("java.lang.Long".equals(paramType.getName())
+                || "long".equals(paramType.getName())) {
+            try {
+                result = Long.parseLong(object);
+            } catch (NumberFormatException ex) {
+            }
+            // Try a setFoo ( boolean )
+        } else if ("java.lang.Integer".equals(paramType.getName())
+                || "int".equals(paramType.getName())) {
+            try {
+                result = new Integer(object);
+            } catch (NumberFormatException ex) {
+            }
+            // Try a setFoo ( boolean )
+        } else if ("java.lang.Boolean".equals(paramType.getName())
+                || "boolean".equals(paramType.getName())) {
+            result = new Boolean(object);
+        } else {
+            log.info("Unknown type " + paramType.getName());
+        }
+        if (result == null) {
+            throw new IllegalArgumentException("Can't convert argument: "
+                    + object +  " to " + paramType );
+        }
+        return result;
+    }
+
+    /**
+     * Converts the first character of the given String into lower-case.
+     *
+     * @param name
+     *            The string to convert
+     * @return String
+     */
+    static String unCapitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        char chars[] = name.toCharArray();
+        chars[0] = Character.toLowerCase(chars[0]);
+        return new String(chars);
+    }
+
+    /**
+     * Check if this class is one of the supported types. If the class is
+     * supported, returns true. Otherwise, returns false.
+     *
+     * @param ret
+     *            The class to check
+     * @return boolean True if class is supported
+     */
+    static boolean supportedType(Class<?> ret) {
+        for (int i = 0; i < supportedTypes.length; i++) {
+            if (ret == supportedTypes[i]) {
+                return true;
+            }
+        }
+        if (isBeanCompatible(ret)) {
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Check if this class conforms to JavaBeans specifications. If the class is
+     * conformant, returns true.
+     *
+     * @param javaType
+     *            The class to check
+     * @return boolean True if the class is compatible.
+     */
+    static boolean isBeanCompatible(Class<?> javaType) {
+        // Must be a non-primitive and non array
+        if (javaType.isArray() || javaType.isPrimitive()) {
+            return false;
+        }
+
+        // Anything in the java or javax package that
+        // does not have a defined mapping is excluded.
+        if (javaType.getName().startsWith("java.")
+                || javaType.getName().startsWith("javax.")) {
+            return false;
+        }
+
+        try {
+            javaType.getConstructor(new Class[] {});
+        } catch (java.lang.NoSuchMethodException e) {
+            return false;
+        }
+
+        // Make sure superclass is compatible
+        Class<?> superClass = javaType.getSuperclass();
+        if (superClass != null && superClass != java.lang.Object.class
+                && superClass != java.lang.Exception.class
+                && superClass != java.lang.Throwable.class) {
+            if (!isBeanCompatible(superClass)) {
+                return false;
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Reverse of Introspector.decapitalize
+     */
+    static String capitalize(String name) {
+        if (name == null || name.length() == 0) {
+            return name;
+        }
+        char chars[] = name.toCharArray();
+        chars[0] = Character.toUpperCase(chars[0]);
+        return new String(chars);
+    }
+
+    private boolean ignorable(Method method) {
+        if (Modifier.isStatic(method.getModifiers()))
+            return true;
+        if (!Modifier.isPublic(method.getModifiers())) {
+            return true;
+        }
+        if (method.getDeclaringClass() == Object.class)
+            return true;
+        return false;
+    }
+
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java
new file mode 100644
index 0000000..0a7e9c9
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/HttpCookie.java
@@ -0,0 +1,290 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.test.watchdog;
+
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Date;
+import java.util.Properties;
+import java.util.StringTokenizer;
+
+/**
+ * An object which represents an HTTP cookie.  Can be constructed by
+ * parsing a string from the set-cookie: header.
+ *
+ * Syntax: Set-Cookie: NAME=VALUE; expires=DATE;
+ *             path=PATH; domain=DOMAIN_NAME; secure
+ *
+ * All but the first field are optional.
+ *
+ * @author	Ramesh.Mandava
+ */
+
+public class HttpCookie {
+    private Date expirationDate = null;
+    private String nameAndValue;
+    private String path;
+    private String domain;
+    private boolean isSecure = false;
+    private static boolean defaultSet = true;
+    private static long defExprTime = 100;
+
+    public HttpCookie(String cookieString) {
+	/*
+	System.out.println("Calling default expiration :");
+	getDefaultExpiration();
+	*/
+	parseCookieString(cookieString);
+    }
+
+    //
+    // Constructor for use by the bean
+    //
+    public HttpCookie(Date expirationDate,
+		      String nameAndValue,
+		      String path,
+		      String domain,
+		      boolean isSecure) {
+	this.expirationDate = expirationDate;
+	this.nameAndValue = nameAndValue;
+	this.path = path;
+	this.domain = domain;
+	this.isSecure = isSecure;
+    }
+
+    public HttpCookie(URL url, String cookieString) {
+	parseCookieString(cookieString);
+	applyDefaults(url);
+    }
+
+    /**
+     * Fills in default values for domain, path, etc. from the URL
+     * after creation of the cookie.
+     */
+    private void applyDefaults(URL url) {
+	if (domain == null) {
+	    domain = url.getHost()+":"+((url.getPort() == -1) ? 80 : url.getPort());
+	}
+
+	if (path == null) {
+	    path = url.getFile();
+
+	    // larrylf: The documentation for cookies say that the path is
+	    // by default, the path of the document, not the filename of the
+	    // document.  This could be read as not including that document
+	    // name itself, just its path (this is how NetScape intrprets it)
+	    // so amputate the document name!
+	    int last = path.lastIndexOf("/");
+	    if( last > -1 ) {
+		path = path.substring(0, last);
+	    }
+	}
+    }
+
+
+    /**
+     * Parse the given string into its individual components, recording them
+     * in the member variables of this object.
+     */
+    private void parseCookieString(String cookieString) {
+	StringTokenizer tokens = new StringTokenizer(cookieString, ";");
+
+	if (!tokens.hasMoreTokens()) {
+	    // REMIND: make this robust against parse errors
+	    nameAndValue="=";
+	    return;
+	}
+
+	nameAndValue = tokens.nextToken().trim();
+	
+	while (tokens.hasMoreTokens()) {
+	    String token = tokens.nextToken().trim();
+	
+	    if (token.equalsIgnoreCase("secure")) {
+		isSecure = true;
+	    } else {
+		int equIndex = token.indexOf("=");
+		
+		if (equIndex < 0) {
+		    continue;
+		    // REMIND: malformed cookie
+		}
+		
+		String attr = token.substring(0, equIndex);
+		String val = token.substring(equIndex+1);
+		
+		if (attr.equalsIgnoreCase("path")) {
+		    path = val;
+		} else if (attr.equalsIgnoreCase("domain")) {
+		    if( val.indexOf(".") == 0 ) {
+			// spec seems to allow for setting the domain in
+			// the form 'domain=.eng.sun.com'.  We want to
+			// trim off the leading '.' so we can allow for
+			// both leading dot and non leading dot forms
+			// without duplicate storage.
+			domain = val.substring(1);
+		    } else {
+			domain = val;
+		    }
+		} else if (attr.equalsIgnoreCase("expires")) {
+		    expirationDate = parseExpireDate(val);
+		} else {
+		    // unknown attribute -- do nothing
+		}
+	    }
+	}
+
+	// commented the following out, b/c ok to have no expirationDate
+	// that means that the cookie should last only for that particular
+	// session.
+	//	if (expirationDate == null) {
+	//     	    expirationDate = getDefaultExpiration();
+	//	}
+    }
+
+    /* Returns the default expiration, which is the current time + default
+       expiration as specified in the properties file.
+       This uses reflection to get at the properties file, since Globals is
+       not in the utils/ directory
+       */
+    private Date getDefaultExpiration() {
+	if (defaultSet == false) {
+	    Properties props = new Properties();
+	
+	    try {
+		FileInputStream fin = new FileInputStream("ServerAutoRun.properties");
+		props.load( fin );
+		
+		System.out.println("Got properties from ServerAutoRun.properties");
+		props.list(System.out);
+		
+	    } catch (IOException ex) {
+		System.out.println("HttpCookie getDefaultExpiration : ServerAutoRun.properties not found!" + ex);
+	    }
+	
+		 // defExprTime = props.getProperty("cookies.default.expiration");
+		 defExprTime = Long.parseLong( props.getProperty("cookies.default.expiration") );
+
+	    }
+	    defaultSet = true;
+	
+	return (new Date(System.currentTimeMillis() + defExprTime));
+	
+    }
+
+    //======================================================================
+    //
+    // Accessor functions
+    //
+
+
+
+     public String getNameValue() {
+	return nameAndValue;
+    }
+
+    /**
+     * Returns just the name part of the cookie
+     */
+     public String getName() {
+
+	 // it probably can't have null value, but doesn't hurt much
+	 // to check.
+	 if (nameAndValue == null) {
+	     return "=";
+	 }
+	 int index = nameAndValue.indexOf("=");
+	 return (index < 0) ? "=" : nameAndValue.substring(0, index);
+     }
+
+
+    /**
+     * Returns the domain of the cookie as it was presented
+     */
+     public String getDomain() {
+	// REMIND: add port here if appropriate
+	return domain;
+    }
+
+     public String getPath() {
+	return path;
+    }
+
+     public Date getExpirationDate() {
+	return expirationDate;
+    }
+
+    public boolean hasExpired() {
+	if(expirationDate == null) {
+	    return false;
+	}
+	return (expirationDate.getTime() <= System.currentTimeMillis());
+    }
+
+    /**
+     * Returns true if the cookie has an expiration date (meaning it's
+     * persistent), and if the date nas not expired;
+     */
+    public boolean isSaveable() {
+	return (expirationDate != null)
+	    && (expirationDate.getTime() > System.currentTimeMillis());
+    }
+
+    public boolean isSaveableInMemory() {
+	return ((expirationDate == null) ||
+		(expirationDate != null && expirationDate.getTime() > System.currentTimeMillis()));
+    }
+		
+     public boolean isSecure() {
+	return isSecure;
+    }
+
+    private Date parseExpireDate(String dateString) {
+	// format is wdy, DD-Mon-yyyy HH:mm:ss GMT
+	RfcDateParser parser = new RfcDateParser(dateString);
+	Date theDate = parser.getDate();
+	if (theDate == null) {
+	    // Expire in some intelligent default time
+	    theDate = getDefaultExpiration();
+	}
+	return theDate;
+    }
+
+    public String toString() {
+
+	String result = (nameAndValue == null) ? "=" : nameAndValue;
+	if (expirationDate != null) {
+	    result += "; expires=" + expirationDate;
+	}
+	
+	if (path != null) {
+	    result += "; path=" + path;
+	}
+
+	if (domain != null) {
+	    result += "; domain=" + domain;
+	}
+
+	if (isSecure) {
+	    result += "; secure";
+	}
+
+	return result;
+    }
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java
new file mode 100644
index 0000000..9a7f604
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/RfcDateParser.java
@@ -0,0 +1,103 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.test.watchdog;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * A parser for date strings commonly found in http and email headers that
+ * follow various RFC conventions.  Given a date-string, the parser will
+ * attempt to parse it by trying matches with a set of patterns, returning
+ * null on failure, a Date object on success.
+ *
+ * @author Ramesh.Mandava
+ */
+public class RfcDateParser {
+
+    private boolean isGMT = false;
+
+    static final String[] standardFormats = {
+	"EEEE', 'dd-MMM-yy HH:mm:ss z",   // RFC 850 (obsoleted by 1036)
+	"EEEE', 'dd-MMM-yy HH:mm:ss",     // ditto but no tz. Happens too often
+	"EEE', 'dd-MMM-yyyy HH:mm:ss z",  // RFC 822/1123
+	"EEE', 'dd MMM yyyy HH:mm:ss z",  // REMIND what rfc? Apache/1.1
+	"EEEE', 'dd MMM yyyy HH:mm:ss z", // REMIND what rfc? Apache/1.1
+	"EEE', 'dd MMM yyyy hh:mm:ss z",  // REMIND what rfc? Apache/1.1
+	"EEEE', 'dd MMM yyyy hh:mm:ss z", // REMIND what rfc? Apache/1.1
+	"EEE MMM dd HH:mm:ss z yyyy",      // Date's string output format
+	"EEE MMM dd HH:mm:ss yyyy",	  // ANSI C asctime format()
+	"EEE', 'dd-MMM-yy HH:mm:ss",      // No time zone 2 digit year RFC 1123
+ 	"EEE', 'dd-MMM-yyyy HH:mm:ss"     // No time zone RFC 822/1123
+    };
+
+    /* because there are problems with JDK1.1.6/SimpleDateFormat with
+     * recognizing GMT, we have to create this workaround with the following
+     * hardcoded strings */
+    static final String[] gmtStandardFormats = {
+	"EEEE',' dd-MMM-yy HH:mm:ss 'GMT'",   // RFC 850 (obsoleted by 1036)
+	"EEE',' dd-MMM-yyyy HH:mm:ss 'GMT'",  // RFC 822/1123
+	"EEE',' dd MMM yyyy HH:mm:ss 'GMT'",  // REMIND what rfc? Apache/1.1
+	"EEEE',' dd MMM yyyy HH:mm:ss 'GMT'", // REMIND what rfc? Apache/1.1
+	"EEE',' dd MMM yyyy hh:mm:ss 'GMT'",  // REMIND what rfc? Apache/1.1
+	"EEEE',' dd MMM yyyy hh:mm:ss 'GMT'", // REMIND what rfc? Apache/1.1
+	"EEE MMM dd HH:mm:ss 'GMT' yyyy"      // Date's string output format
+    };
+
+    String dateString;
+
+    public RfcDateParser(String dateString) {
+	this.dateString = dateString.trim();
+	if (this.dateString.indexOf("GMT") != -1) {
+	    isGMT = true;
+	}
+    }
+
+    public Date getDate() {
+
+        int arrayLen = isGMT ? gmtStandardFormats.length : standardFormats.length;
+        for (int i = 0; i < arrayLen; i++) {
+            Date d = null;
+
+            if (isGMT) {
+                d = tryParsing(gmtStandardFormats[i]);
+            } else {
+                d = tryParsing(standardFormats[i]);
+            }
+            if (d != null) {
+                return d;
+            }
+
+        }
+
+        return null;
+    }
+
+    private Date tryParsing(String format) {
+
+	java.text.SimpleDateFormat df = new java.text.SimpleDateFormat(format, Locale.US);
+	if (isGMT) {
+	    df.setTimeZone(TimeZone.getTimeZone("GMT"));
+	}
+	try {
+		return df.parse(dateString);
+	} catch (Exception e) {
+	    return null;
+	}
+    }
+} /* class RfcDateParser */
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java
new file mode 100644
index 0000000..05b481c
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogClient.java
@@ -0,0 +1,213 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.test.watchdog;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.StringReader;
+import java.util.Properties;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import junit.framework.Test;
+import junit.framework.TestResult;
+import junit.framework.TestSuite;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+public class WatchdogClient {
+
+  protected String goldenDir;
+  protected String testMatch;
+  protected String file;
+  protected String[] exclude = null;
+  protected String[] slow =
+  {
+      "SingleModelTest" // slow
+  };
+
+  protected String targetMatch;
+
+  protected int port;
+
+  Properties props = new Properties();
+
+  protected void beforeSuite() {
+  }
+
+  protected void afterSuite(TestResult res) {
+  }
+
+  public Test getSuite() {
+      return getSuite(port);
+  }
+
+  public static class NullResolver implements EntityResolver {
+      public InputSource resolveEntity (String publicId,
+                                                 String systemId)
+          throws SAXException, IOException
+      {
+          return new InputSource(new StringReader(""));
+      }
+  }
+
+  /** Read XML as DOM.
+   */
+  public static Document readXml(InputStream is)
+      throws SAXException, IOException, ParserConfigurationException
+  {
+      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
+      dbf.setValidating(false);
+      dbf.setIgnoringComments(false);
+      dbf.setIgnoringElementContentWhitespace(true);
+      DocumentBuilder db = null;
+      db = dbf.newDocumentBuilder();
+      db.setEntityResolver( new NullResolver() );
+      Document doc = db.parse(is);
+      return doc;
+  }
+
+  /**
+   * Return a test suite for running a watchdog-like
+   * test file.
+   *
+   * @param base base dir for the watchdog dir
+   * @param testMatch Prefix of tests to be run
+   * @return
+   */
+  public Test getSuite(int port) {
+    TestSuite tests = new WatchdogTests();
+    tests.setName(this.getClass().getSimpleName());
+
+    props.setProperty("port", Integer.toString(port));
+    props.setProperty("host", "localhost");
+    props.setProperty("wgdir",
+        goldenDir);
+
+
+    try {
+      Document doc = readXml(new FileInputStream(file));
+      Element docE = doc.getDocumentElement();
+      NodeList targetsL = docE.getElementsByTagName("target");
+      for (int i = 0; i < targetsL.getLength(); i++) {
+        Element target = (Element) targetsL.item(i);
+        String targetName = target.getAttribute("name");
+        if (targetMatch != null && !targetName.equals(targetMatch)) {
+            continue;
+        }
+
+        // Tests are duplicated
+        //TestSuite targetSuite = new TestSuite(targetName);
+
+        NodeList watchDogL = target.getElementsByTagName("watchdog");
+        for (int j = 0; j < watchDogL.getLength(); j++) {
+          Element watchE = (Element) watchDogL.item(j);
+          String testName = watchE.getAttribute("testName");
+          if (single != null && !testName.equals(single)) {
+              continue;
+          }
+          if (testMatch != null) {
+              if (!testName.startsWith(testMatch)) {
+                  continue;
+              }
+          }
+          if (exclude != null) {
+              boolean found = false;
+              for (String e: exclude) {
+                  if (e.equals(testName)) {
+                      found = true;
+                      break;
+                  }
+              }
+              if (found) {
+                  continue;
+              }
+          }
+          testName = testName + "(" + this.getClass().getName() + ")";
+          WatchdogTestCase test = new WatchdogTestCase(watchE, props, testName);
+          tests.addTest(test);
+          if (single != null) {
+              singleTest = test;
+              break;
+          }
+        }
+      }
+
+    } catch (IOException e) {
+        e.printStackTrace();
+    } catch (SAXException e) {
+        e.printStackTrace();
+    } catch (ParserConfigurationException e) {
+        e.printStackTrace();
+    }
+    return tests;
+  }
+
+  // --------- Inner classes -------------
+
+  protected String getWatchdogdir() {
+      String path = System.getProperty("watchdog.home");
+      if (path != null) {
+          return path;
+      }
+      path = "..";
+      for (int i = 0; i < 10; i++) {
+          File f = new File(path + "/watchdog");
+          if (f.exists()) {
+              return f.getAbsolutePath();
+          }
+          path = path + "/..";
+      }
+      return null;
+  }
+
+  public class WatchdogTests extends TestSuite {
+      public void run(TestResult res) {
+          beforeSuite();
+          super.run(res);
+          afterSuite(res);
+      }
+  }
+
+  // Support for running a single test in the suite
+
+  protected String single;
+  WatchdogTestCase singleTest;
+
+  public int countTestCases() {
+      return 1;
+  }
+
+  public void run(TestResult result) {
+      getSuite();
+      if (singleTest != null) {
+          beforeSuite();
+          singleTest.run(result);
+          afterSuite(result);
+      }
+  }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java
new file mode 100644
index 0000000..2464f33
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogHttpClient.java
@@ -0,0 +1,411 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.test.watchdog;
+
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.SocketException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Vector;
+
+
+public class WatchdogHttpClient {
+    private static final String CRLF         = "\r\n";
+    private static final int LINE_FEED       = 10;
+
+    static int debug = 0;
+
+    public static void dispatch(WatchdogTestImpl client) throws Exception {
+        HashMap requestHeaders = client.requestHeaders;
+        String host = client.host;
+        int port = client.port;
+        String content = client.content;
+        String request = client.request;
+
+        // XXX headers are ignored
+        Socket socket;
+        try {
+            socket = new Socket( host, port );
+        } catch (IOException ex) {
+            System.out.println( " Socket Exception: " + ex );
+            return;
+        }
+        socket.setSoTimeout(10000);
+
+        //socket obtained, rebuild the request.
+        rebuildRequest(client, client.request, socket);
+
+        InputStream in = new CRBufferedInputStream( socket.getInputStream() );
+
+        // Write the request
+        socket.setSoLinger( true, 1000 );
+
+        OutputStream out = new BufferedOutputStream(
+                               socket.getOutputStream() );
+        StringBuffer reqbuf = new StringBuffer( 128 );
+
+        // set the Host header
+        client.setHeaderDetails( "Host:" + host + ":" + port, requestHeaders, true );
+
+        // set the Content-Length header
+        if ( content != null ) {
+            client.setHeaderDetails( "Content-Length:" + content.length(),
+                              requestHeaders, true );
+        }
+
+        // set the Cookie header
+        if ( client.testSession != null ) {
+            client.cookieController = ( CookieController ) client.sessionHash.get( client.testSession );
+
+            if ( client.cookieController != null ) {
+
+                String releventCookieString = client.cookieController.applyRelevantCookies( client.requestURL );
+
+                if ( ( releventCookieString != null ) && ( !releventCookieString.trim().equals( "" ) ) ) {
+                    client.setHeaderDetails( "Cookie:" + releventCookieString, requestHeaders, true );
+                }
+            }
+        }
+
+        if ( debug > 0 ) {
+            System.out.println( " REQUEST: " + request );
+        }
+        reqbuf.append( client.request ).append( CRLF );
+
+        // append all request headers
+        if ( !requestHeaders.isEmpty() ) {
+            Iterator iter = requestHeaders.keySet().iterator();
+
+            while ( iter.hasNext() ) {
+                StringBuffer tmpBuf = new StringBuffer(32);
+                String headerKey = ( String ) iter.next();
+                        ArrayList values = (ArrayList) requestHeaders.get( headerKey );
+                        String[] value = (String[]) values.toArray( new String[ values.size() ] );
+                tmpBuf.append( headerKey ).append(": ");
+                        for ( int i = 0; i < value.length; i++ ) {
+                    if ((i + 1) == value.length) {
+                                    tmpBuf.append( value[ i ] );
+                    } else {
+                        tmpBuf.append( value[ i ] ).append(", ");
+                    }
+                        }
+                            if ( debug > 0 ) {
+                                System.out.println( " REQUEST HEADER: " + tmpBuf.toString());
+                            }
+                tmpBuf.append( CRLF );
+                reqbuf.append(tmpBuf.toString());
+            }
+        }
+
+        /*
+
+        if ( ( testSession != null ) && ( sessionHash.get( testSession ) != null ) ) {
+            System.out.println("Sending Session Id : " + (String)sessionHash.get( testSession ) );
+            pw.println("JSESSIONID:" + (String)sessionHash.get( testSession) );
+        }
+
+        */
+
+        if ( request.indexOf( "HTTP/1." ) > -1 ) {
+            reqbuf.append( "" ).append( CRLF );
+        }
+
+        // append request content
+        if ( content != null ) {
+            reqbuf.append( content );
+            // XXX no CRLF at the end -see HTTP specs!
+        }
+
+        byte[] reqbytes = reqbuf.toString().getBytes();
+
+        try {
+            // write the request
+            out.write( reqbytes, 0, reqbytes.length );
+            out.flush();
+        } catch ( Exception ex1 ) {
+            System.out.println( " Error writing request " + ex1 );
+                if ( debug > 0 ) {
+                        System.out.println( "Message: " + ex1.getMessage() );
+                        ex1.printStackTrace();
+                }
+        }
+
+        // read the response
+        try {
+
+                client.responseLine = read( in );
+
+                if ( debug > 0 ) {
+                        System.out.println( " RESPONSE STATUS-LINE: " + client.responseLine );
+                }
+
+                client.headers = parseHeaders( client, in );
+
+            byte[] result = readBody( in );
+
+            if ( result != null ) {
+                client.responseBody = result;
+                        if ( debug > 0 ) {
+                            System.out.println( " RESPONSE BODY:\n" + new String( client.responseBody ) );
+                        }
+                }
+
+        } catch ( SocketException ex ) {
+            System.out.println( " Socket Exception: " + ex );
+        } finally {
+                if ( debug > 0 ) {
+                        System.out.println( " closing socket" );
+                }
+                socket.close();
+                socket = null;
+            }
+
+    }
+
+    /**
+     * <code>readBody</code> reads the body of the response
+     * from the InputStream.
+     *
+     * @param input an <code>InputStream</code>
+     * @return a <code>byte[]</code> representation of the response
+     */
+    private static byte[] readBody( InputStream input ) {
+        StringBuffer sb = new StringBuffer( 255 );
+        while ( true ) {
+            try {
+                int ch = input.read();
+
+                if ( ch < 0 ) {
+                    if ( sb.length() == 0 ) {
+                        return ( null );
+                    } else {
+                        break;
+                    }
+                }
+                sb.append( ( char ) ch );
+
+            } catch ( IOException ex ) {
+                return null;
+            }
+        }
+        return sb.toString().getBytes();
+    }
+
+
+    /**
+     * Read a line from the specified servlet input stream, and strip off
+     * the trailing carriage return and newline (if any).  Return the remaining
+     * characters that were read as a string.7
+     *
+     * @returns The line that was read, or <code>null</code> if end of file
+     *  was encountered
+     *
+     * @exception IOException if an input/output error occurred
+     */
+    private static String read( InputStream input ) throws IOException {
+        // Read the next line from the input stream
+        StringBuffer sb = new StringBuffer();
+
+        while ( true ) {
+            try {
+                int ch = input.read();
+                //              System.out.println("XXX " + (char)ch );
+                if ( ch < 0 ) {
+                    if ( sb.length() == 0 ) {
+                        if ( debug > 0 )
+                            System.out.println( " Error reading line " + ch + " " + sb.toString() );
+                        return "";
+                    } else {
+                        break;
+                    }
+                } else if ( ch == LINE_FEED ) {
+                    break;
+                }
+
+                sb.append( ( char ) ch );
+            } catch ( IOException ex ) {
+                System.out.println( " Error reading : " + ex );
+                debug = 1;
+
+                if ( debug > 0 ) {
+                    System.out.println( "Partial read: " + sb.toString() );
+                    ex.printStackTrace();
+                }
+                input.close();
+                break;
+            }
+        }
+        return  sb.toString();
+    }
+
+
+    // ==================== Code from JSERV !!! ====================
+    /**
+     * Parse the incoming HTTP request headers, and set the corresponding
+     * request properties.
+     *
+     *
+     * @exception IOException if an input/output error occurs
+     */
+    private static HashMap parseHeaders( WatchdogTestImpl client, InputStream is ) throws IOException {
+        HashMap headers = new HashMap();
+        client.cookieVector = new Vector();
+
+        while ( true ) {
+            // Read the next header line
+            String line = read( is );
+
+            if ( ( line == null ) || ( line.length() < 1 ) ) {
+                break;
+            }
+
+            client.parseHeader( line, headers, false );
+
+            if ( debug > 0 ) {
+                System.out.println( " RESPONSE HEADER: " + line );
+            }
+
+        }
+
+        if ( client.testSession != null ) {
+            client.cookieController = ( CookieController ) client.sessionHash.get( client.testSession );
+
+            if ( client.cookieController != null ) {
+                client.cookieController.recordAnyCookies( client.cookieVector, client.requestURL );
+            }
+        }
+
+        return headers;
+    }
+
+
+    /**
+     * Private utility method to 'massage' a request string that
+     * may or may not have replacement markers for the request parameters.
+     *
+     * @param req the request to manipulate
+     * @param socket local socket.  Used to rebuild specified query strings.
+     *
+     * @exception Exception if an error occurs
+     */
+    private static void rebuildRequest(WatchdogTestImpl client, String req, Socket socket) throws Exception {
+        client.request = client.replaceMarkers(req, socket );
+        String addressString = client.request.substring( client.request.indexOf( "/" ), client.request.indexOf( "HTTP" ) ).trim();
+
+        if ( addressString.indexOf( "?" ) > -1 ) {
+            addressString = addressString.substring( 0, addressString.indexOf( "?" ) ) ;
+        }
+
+        client.requestURL = new URL( "http", client.host, client.port, addressString );
+    }
+
+
+    /**
+     * <code>CRBufferedInputStream</code> is a modified version of
+     * the java.io.BufferedInputStream class.  The fill code is
+     * the same, but the read is modified in that if a carriage return
+     * is found in the response stream from the target server,
+     * it will skip that byte and return the next in the stream.
+     */
+    private static class CRBufferedInputStream extends BufferedInputStream {
+        private static final int CARRIAGE_RETURN = 13;
+
+        private static final int DEFAULT_BUFFER = 2048;
+
+        /**
+         * Creates a new <code>CRBufferedInputStream</code> instance.
+         *
+         * @param in an <code>InputStream</code> value
+         */
+        public CRBufferedInputStream( InputStream in ) {
+            super( in, DEFAULT_BUFFER );
+        }
+
+        /**
+         * <code>read</code> reads a single byte value per call.
+         * If, the byte read, is a carriage return, the next byte
+         * in the stream in returned instead.
+         *
+         * @return an <code>int</code> value
+         * @exception IOException if an error occurs
+         */
+        public int read() throws IOException {
+            if ( in == null ) {
+                throw new IOException ( "Stream closed" );
+            }
+            if ( pos >= count ) {
+                fill();
+                if ( pos >= count ) {
+                    return -1;
+                }
+            }
+            int val = buf[pos++] & 0xff;
+            if ( val == CARRIAGE_RETURN ) {
+                if (pos >= count) {
+                    fill();
+                    if (pos >= count) {
+                       return -1;
+                    }
+                }
+                return buf[pos++] & 0xff;
+            }
+            return val;
+        }
+
+        /**
+         * <code>fill</code> is used to fill the internal
+         * buffer used by this BufferedInputStream class.
+         *
+         * @exception IOException if an error occurs
+         */
+        private void fill() throws IOException {
+            if (markpos < 0) {
+                pos = 0;        /* no mark: throw away the buffer */
+            } else if (pos >= buf.length)  {/* no room left in buffer */
+                if (markpos > 0) {  /* can throw away early part of the buffer */
+                    int sz = pos - markpos;
+                    System.arraycopy(buf, markpos, buf, 0, sz);
+                    pos = sz;
+                    markpos = 0;
+                } else if (buf.length >= marklimit) {
+                    markpos = -1;   /* buffer got too big, invalidate mark */
+                    pos = 0;    /* drop buffer contents */
+                } else {        /* grow buffer */
+                    int nsz = pos * 2;
+                    if (nsz > marklimit)
+                        nsz = marklimit;
+                    byte nbuf[] = new byte[nsz];
+                    System.arraycopy(buf, 0, nbuf, 0, pos);
+                    buf = nbuf;
+                }
+            }
+            count = pos;
+            int n = in.read(buf, pos, buf.length - pos);
+            if (n > 0) {
+                count = n + pos;
+            }
+        }
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java
new file mode 100644
index 0000000..8da9b35
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestCase.java
@@ -0,0 +1,93 @@
+/*
+ */
+package org.apache.tomcat.test.watchdog;
+
+import java.util.Properties;
+
+import junit.framework.AssertionFailedError;
+import junit.framework.Test;
+import junit.framework.TestResult;
+
+import org.w3c.dom.Element;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+
+public class WatchdogTestCase implements Test {
+    String testName;
+
+    Element watchE;
+
+    private Properties props;
+
+    private WatchdogClient wc;
+
+    public WatchdogTestCase() {
+
+    }
+
+    public WatchdogTestCase(Element watchE, Properties props, String testName) {
+        this.testName = testName;
+        this.watchE = watchE;
+        this.props = props;
+    }
+
+    public int countTestCases() {
+        return 1;
+    }
+
+    public String getName() {
+        return testName == null ? "WatchdogTest" : testName;
+    }
+
+    public String toString() {
+        return getName();
+    }
+
+    public void testDummy() {
+    }
+
+    public void run(TestResult res) {
+        if (watchE == null) {
+            res.endTest(this);
+            return;
+        }
+        WatchdogTestImpl test = new WatchdogTestImpl();
+        NamedNodeMap attrs = watchE.getAttributes();
+
+        for (int i = 0; i < attrs.getLength(); i++) {
+            Node n = attrs.item(i);
+            String name = n.getNodeName();
+            String value = n.getNodeValue();
+            value = AntProperties.replaceProperties(value, props, null);
+            try {
+                new DynamicObject(test.getClass()).setProperty(test,
+                        name, value);
+            } catch (Exception e) {
+                // TODO Auto-generated catch block
+                e.printStackTrace();
+            }
+        }
+
+        try {
+            res.startTest(this);
+            new DynamicObject(test.getClass()).invoke(test, "execute");
+        } catch (Throwable e) {
+            res.addError(this, e);
+            // res.stop();
+        }
+
+        if (test.passCount == 1) {
+            res.endTest(this);
+            return;
+        } else {
+            if (test.lastError == null) {
+                res.addFailure(this, new AssertionFailedError(test.request
+                        + " " + test.description + "\n" + test.resultOut));
+            } else {
+                res.addError(this, test.lastError);
+            }
+        }
+        res.endTest(this);
+    }
+
+}
diff --git a/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestImpl.java b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestImpl.java
new file mode 100644
index 0000000..cda165b
--- /dev/null
+++ b/modules/tomcat-lite/test/org/apache/tomcat/test/watchdog/WatchdogTestImpl.java
@@ -0,0 +1,1172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+
+/**
+ * @Author Costin, Ramesh.Mandava
+ */
+
+package org.apache.tomcat.test.watchdog;
+
+import java.io.BufferedInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+import java.util.Vector;
+
+import org.apache.tomcat.lite.io.Hex;
+
+// derived from Jsp
+
+public class WatchdogTestImpl {
+
+    int failureCount = 0;
+
+    int passCount = 0;
+
+    Throwable lastError;
+
+    boolean hasFailed = false;
+
+    String prefix = "http";
+
+    String host = "localhost";
+
+    String localHost = null;
+
+    String localIP = null;
+
+    int port = 8080;
+
+    int debug = 0;
+
+    String description = "No description";
+
+    String request;
+
+    HashMap requestHeaders = new HashMap();
+
+    String content;
+
+    // true if task is nested
+    private boolean nested = false;
+
+    // Expected response
+    boolean magnitude = true;
+
+    boolean exactMatch = false;
+
+    // expect a response body
+    boolean expectResponseBody = true;
+
+    // Match the body against a golden file
+    String goldenFile;
+
+    // Match the body against a string
+    String responseMatch;
+
+    // the response should include the following headers
+    HashMap expectHeaders = new HashMap();
+
+    // Headers that should not be found in response
+    HashMap unexpectedHeaders = new HashMap();
+
+    // Match request line
+    String returnCode = "";
+
+    String returnCodeMsg = "";
+
+    // Actual response
+    String responseLine;
+
+    byte[] responseBody;
+
+    HashMap headers;
+
+    // For Report generation
+    StringBuffer resultOut = new StringBuffer();
+
+    boolean firstTask = false;
+
+    boolean lastTask = false;
+
+    String expectedString;
+
+    String actualString;
+
+    String testName;
+
+    String assertion;
+
+    String testStrategy;
+
+    // For Session Tracking
+    static Hashtable sessionHash;
+
+    static Hashtable cookieHash;
+
+    String testSession;
+
+    Vector cookieVector;
+
+    URL requestURL;
+
+    CookieController cookieController;
+
+    /**
+     * Creates a new <code>GTest</code> instance.
+     *
+     */
+    public WatchdogTestImpl() {
+    }
+
+    /**
+     * <code>setTestSession</code> adds a CookieController for the value of
+     * sessionName
+     *
+     * @param sessionName
+     *            a <code>String</code> value
+     */
+    public void setTestSession(String sessionName) {
+        testSession = sessionName;
+
+        if (sessionHash == null) {
+            sessionHash = new Hashtable();
+        } else if (sessionHash.get(sessionName) == null) {
+            sessionHash.put(sessionName, new CookieController());
+        }
+    }
+
+    /**
+     * <code>setTestName</code> sets the current test name.
+     *
+     * @param tn
+     *            current testname.
+     */
+    public void setTestName(String tn) {
+        testName = tn;
+    }
+
+    /**
+     * <code>setAssertion</code> sets the assertion text for the current test.
+     *
+     * @param assertion
+     *            assertion text
+     */
+    public void setAssertion(String assertion) {
+        this.assertion = assertion;
+    }
+
+    /**
+     * <code>setTestStrategy</code> sets the test strategy for the current test.
+     *
+     * @param strategy
+     *            test strategy text
+     */
+    public void setTestStrategy(String strategy) {
+        testStrategy = strategy;
+    }
+
+    /**
+     * <code>getTestName</code> returns the current test name.
+     *
+     * @return a <code>String</code> value
+     */
+    public String getTestName() {
+        return testName;
+    }
+
+    /**
+     * <code>getAssertion</code> returns the current assertion text.
+     *
+     * @return a <code>String</code> value
+     */
+    public String getAssertion() {
+        return assertion;
+    }
+
+    /**
+     * <code>getTestStrategy</code> returns the current test strategy test.
+     *
+     * @return a <code>String</code> value
+     */
+    public String getTestStrategy() {
+        return testStrategy;
+    }
+
+    /**
+     * <code>setFirstTask</code> denotes that current task being executed is the
+     * first task within the list.
+     *
+     * @param a
+     *            <code>boolean</code> value
+     */
+    public void setFirstTask(boolean val) {
+        firstTask = val;
+    }
+
+    /**
+     * <code>setLastTask</code> denotes that the current task being executed is
+     * the last task within the list.
+     *
+     * @param a
+     *            <code>boolean</code> value
+     */
+    public void setLastTask(boolean val) {
+        lastTask = val;
+    }
+
+    /**
+     * <code>setPrefix</code> sets the protocol prefix. Defaults to "http"
+     *
+     * @param prefix
+     *            Either http or https
+     */
+    public void setPrefix(String prefix) {
+        this.prefix = prefix;
+    }
+
+    /**
+     * <code>setHost</code> sets hostname where the target server is running.
+     * Defaults to "localhost"
+     *
+     * @param h
+     *            a <code>String</code> value
+     */
+    public void setHost(String h) {
+        this.host = h;
+    }
+
+    /**
+     * <code>setPort</code> sets the port that the target server is listening
+     * on. Defaults to "8080"
+     *
+     * @param portS
+     *            a <code>String</code> value
+     */
+    public void setPort(String portS) {
+        this.port = Integer.valueOf(portS).intValue();
+    }
+
+    /**
+     * <code>setExactMatch</code> determines if a byte-by-byte comparsion is
+     * made of the server's response and the test's goldenFile, or if a token
+     * comparison is made. By default, only a token comparison is made
+     * ("false").
+     *
+     * @param exact
+     *            a <code>String</code> value
+     */
+    public void setExactMatch(String exact) {
+        exactMatch = Boolean.valueOf(exact).booleanValue();
+    }
+
+    /**
+     * <code>setContent</code> String value upon which the request header
+     * Content-Length is based upon.
+     *
+     * @param s
+     *            a <code>String</code> value
+     */
+    public void setContent(String s) {
+        this.content = s;
+    }
+
+    /**
+     * <code>setDebug</code> enables debug output. By default, this is disabled
+     * ( value of "0" ).
+     *
+     * @param debugS
+     *            a <code>String</code> value
+     */
+    public void setDebug(String debugS) {
+        debug = Integer.valueOf(debugS).intValue();
+    }
+
+    /**
+     * <code>setMagnitude</code> Expected return value of the test execution.
+     * Defaults to "true"
+     *
+     * @param magnitudeS
+     *            a <code>String</code> value
+     */
+    public void setMagnitude(String magnitudeS) {
+        magnitude = Boolean.valueOf(magnitudeS).booleanValue();
+    }
+
+    /**
+     * <code>setGoldenFile</code> Sets the goldenfile that will be used to
+     * validate the server's response.
+     *
+     * @param s
+     *            fully qualified path and filename
+     */
+    public void setGoldenFile(String s) {
+        this.goldenFile = s;
+    }
+
+    /**
+     * <code>setExpectResponseBody</code> sets a flag to indicate if a response
+     * body is expected from the server or not
+     *
+     * @param b
+     *            a <code>boolean</code> value
+     */
+    public void setExpectResponseBody(boolean b) {
+        this.expectResponseBody = b;
+    }
+
+    /**
+     * <code>setExpectHeaders</code> Configures GTest to look for the header
+     * passed in the server's response.
+     *
+     * @param s
+     *            a <code>String</code> value in the format of
+     *            <header-field>:<header-value>
+     */
+    public void setExpectHeaders(String s) {
+        this.expectHeaders = new HashMap();
+        StringTokenizer tok = new StringTokenizer(s, "|");
+        while (tok.hasMoreElements()) {
+            String header = (String) tok.nextElement();
+            setHeaderDetails(header, expectHeaders, false);
+        }
+    }
+
+    /**
+     * <code>setUnexpectedHeaders</code> Configures GTest to look for the header
+     * passed to validate that it doesn't exist in the server's response.
+     *
+     * @param s
+     *            a <code>String</code> value in the format of
+     *            <header-field>:<header-value>
+     */
+    public void setUnexpectedHeaders(String s) {
+        this.unexpectedHeaders = new HashMap();
+        setHeaderDetails(s, unexpectedHeaders, false);
+    }
+
+    public void setNested(String s) {
+        nested = Boolean.valueOf(s).booleanValue();
+    }
+
+    /**
+     * <code>setResponseMatch</code> Match the passed value in the server's
+     * response.
+     *
+     * @param s
+     *            a <code>String</code> value
+     */
+    public void setResponseMatch(String s) {
+        this.responseMatch = s;
+    }
+
+    /**
+     * <code>setRequest</code> Sets the HTTP/HTTPS request to be sent to the
+     * target server Ex. GET /servlet_path/val HTTP/1.0
+     *
+     * @param s
+     *            a <code>String</code> value in the form of METHOD PATH
+     *            HTTP_VERSION
+     */
+    public void setRequest(String s) {
+        this.request = s;
+    }
+
+    /**
+     * <code>setReturnCode</code> Sets the expected return code from the
+     * server's response.
+     *
+     * @param code
+     *            a valid HTTP response status code
+     */
+    public void setReturnCode(String code) {
+        this.returnCode = code;
+    }
+
+    /**
+     * Describe <code>setReturnCodeMsg</code> Sets the expected return message
+     * to be found in the server's response.
+     *
+     * @param code
+     *            a valid HTTP resonse status code
+     * @param message
+     *            a <code>String</code> value
+     */
+    public void setReturnCodeMsg(String message) {
+        this.returnCodeMsg = message;
+    }
+
+    /**
+     * <code>setRequestHeaders</code> Configures the request headers GTest
+     * should send to the target server.
+     *
+     * @param s
+     *            a <code>String</code> value in for format of
+     *            <field-name>:<field-value>
+     */
+    public void setRequestHeaders(String s) {
+        requestHeaders = new HashMap();
+        StringTokenizer tok = new StringTokenizer(s, "|");
+        while (tok.hasMoreElements()) {
+            String header = (String) tok.nextElement();
+            setHeaderDetails(header, requestHeaders, true);
+        }
+    }
+
+    // Inner tests are not used currently, can be reworked
+
+    // /**
+    // * Add a Task to this container
+    // *
+    // * @param Task to add
+    // */
+    // public void addTask(Task task) {
+    // children.add(task);
+    // }
+
+    /**
+     * <code>execute</code> Executes the test.
+     *
+     * @exception BuildException
+     *                if an error occurs
+     */
+    public void execute() {
+
+        try {
+
+            if (resultOut != null && !nested) {
+                resultOut.append("\ntestName: " + testName);
+                resultOut.append("\nreq: " + request);
+                resultOut.append("\nassertion: " + assertion);
+                resultOut.append("\ntestStrategy: " + testStrategy);
+            }
+
+            WatchdogHttpClient.dispatch(this);
+
+            hasFailed = !checkResponse(magnitude);
+
+            // if ( !children.isEmpty() ) {
+            // Iterator iter = children.iterator();
+            // while (iter.hasNext()) {
+            // Task task = (Task) iter.next();
+            // task.perform();
+            // }
+            // }
+
+            if (!hasFailed && !nested) {
+                passCount++;
+                if (resultOut != null) {
+                    resultOut.append("<result>PASS</result>\n");
+                }
+//                System.out.println(" PASSED " + testName + "        ("
+//                        + request + ")");
+            } else if (hasFailed && !nested) {
+                failureCount++;
+                if (resultOut != null) {
+                    resultOut.append("<result>FAIL</result>\n");
+                }
+                System.out.println(" FAILED " + testName + "\n        ("
+                        + request + ")\n" + resultOut.toString());
+            }
+
+        } catch (Exception ex) {
+            failureCount++;
+            System.out.println(" FAIL " + description + " (" + request + ")");
+            lastError = ex;
+            ex.printStackTrace();
+        } finally {
+            if (!nested) {
+                hasFailed = false;
+            }
+        }
+    }
+
+    /**
+     * <code>checkResponse</code> Executes various response checking mechanisms
+     * against the server's response. Checks include:
+     * <ul>
+     * <li>expected headers
+     * <li>unexpected headers
+     * <li>return codes and messages in the Status-Line
+     * <li>response body comparison againt a goldenfile
+     * </ul>
+     *
+     * @param testCondition
+     *            a <code>boolean</code> value
+     * @return a <code>boolean</code> value
+     * @exception Exception
+     *                if an error occurs
+     */
+    private boolean checkResponse(boolean testCondition) throws Exception {
+        boolean match = false;
+
+        if (responseLine != null && !"".equals(responseLine)) {
+            // If returnCode doesn't match
+            if (responseLine.indexOf("HTTP/1.") > -1) {
+
+                if (!returnCode.equals("")) {
+                    boolean resCode = (responseLine.indexOf(returnCode) > -1);
+                    boolean resMsg = (responseLine.indexOf(returnCodeMsg) > -1);
+
+                    if (returnCodeMsg.equals("")) {
+                        match = resCode;
+                    } else {
+                        match = (resCode && resMsg);
+                    }
+
+                    if (match != testCondition) {
+
+                        if (resultOut != null) {
+                            String expectedStatusCode = "<expectedStatusCode>"
+                                    + returnCode + "</expectedReturnCode>\n";
+                            String expectedReasonPhrase = "<expectedReasonPhrase>"
+                                    + returnCodeMsg + "</expectedReasonPhrase>";
+                            actualString = "<actualStatusLine>" + responseLine
+                                    + "</actualStatusLine>\n";
+                            resultOut.append(expectedStatusCode);
+                            resultOut.append(expectedReasonPhrase);
+                            resultOut.append(actualString);
+                        }
+
+                        return false;
+                    }
+                }
+            } else {
+                resultOut.append("\n<failure>No response or invalid response: "
+                        + responseLine + "</failure>");
+                return false;
+            }
+        } else {
+            resultOut.append("\n<failure>No response from server</failure>");
+            return false;
+        }
+
+        /*
+         * Check for headers the test expects to be in the server's response
+         */
+
+        // Duplicate set of response headers
+        HashMap copiedHeaders = cloneHeaders(headers);
+
+        // used for error reporting
+        String currentHeaderField = null;
+        String currentHeaderValue = null;
+
+        if (!expectHeaders.isEmpty()) {
+            boolean found = false;
+            String expHeader = null;
+
+            if (!headers.isEmpty()) {
+                Iterator expectIterator = expectHeaders.keySet().iterator();
+                while (expectIterator.hasNext()) {
+                    found = false;
+                    String expFieldName = (String) expectIterator.next();
+                    currentHeaderField = expFieldName;
+                    ArrayList expectValues = (ArrayList) expectHeaders
+                            .get(expFieldName);
+                    Iterator headersIterator = copiedHeaders.keySet()
+                            .iterator();
+
+                    while (headersIterator.hasNext()) {
+                        String headerFieldName = (String) headersIterator
+                                .next();
+                        ArrayList headerValues = (ArrayList) copiedHeaders
+                                .get(headerFieldName);
+
+                        // compare field names and values in an HTTP 1.x
+                        // compliant fashion
+                        if ((headerFieldName.equalsIgnoreCase(expFieldName))) {
+                            int hSize = headerValues.size();
+                            int eSize = expectValues.size();
+
+                            // number of expected headers found in server
+                            // response
+                            int numberFound = 0;
+
+                            for (int i = 0; i < eSize; i++) {
+                                currentHeaderValue = (String) expectValues
+                                        .get(i);
+
+                                /*
+                                 * Handle the Content-Type header appropriately
+                                 * based on the the test is configured to look
+                                 * for.
+                                 */
+                                if (currentHeaderField
+                                        .equalsIgnoreCase("content-type")) {
+                                    String resVal = (String) headerValues
+                                            .get(0);
+                                    if (currentHeaderValue.indexOf(';') > -1) {
+                                        if (currentHeaderValue.equals(resVal)) {
+                                            numberFound++;
+                                            headerValues.remove(0);
+                                        }
+                                    } else if (resVal
+                                            .indexOf(currentHeaderValue) > -1) {
+                                        numberFound++;
+                                        headerValues.remove(0);
+                                    }
+                                } else if (currentHeaderField
+                                        .equalsIgnoreCase("location")) {
+                                    String resVal = (String) headerValues
+                                            .get(0);
+                                    int idx = currentHeaderValue
+                                            .indexOf(":80/");
+                                    if (idx > -1) {
+                                        String tempValue = currentHeaderValue
+                                                .substring(0, idx)
+                                                + currentHeaderValue
+                                                        .substring(idx + 3);
+                                        if (currentHeaderValue.equals(resVal)
+                                                || tempValue.equals(resVal)) {
+                                            numberFound++;
+                                            headerValues.remove(0);
+                                        }
+                                    } else {
+                                        if (currentHeaderValue.equals(resVal)) {
+                                            numberFound++;
+                                            headerValues.remove(0);
+                                        }
+                                    }
+                                } else if (headerValues
+                                        .contains(currentHeaderValue)) {
+                                    numberFound++;
+                                    headerValues.remove(headerValues
+                                            .indexOf(currentHeaderValue));
+                                }
+                            }
+                            if (numberFound == eSize) {
+                                found = true;
+                            }
+                        }
+                    }
+                    if (!found) {
+                        /*
+                         * Expected headers not found in server response. Break
+                         * the processing loop.
+                         */
+                        break;
+                    }
+                }
+            }
+
+            if (!found) {
+                StringBuffer actualBuffer = new StringBuffer(128);
+                if (resultOut != null) {
+                    expectedString = "<expectedHeaderNotFound>"
+                            + currentHeaderField + ": " + currentHeaderValue
+                            + "</expectedHeader>\n";
+                }
+                if (!headers.isEmpty()) {
+                    Iterator iter = headers.keySet().iterator();
+                    while (iter.hasNext()) {
+                        String headerName = (String) iter.next();
+                        ArrayList vals = (ArrayList) headers.get(headerName);
+                        String[] val = (String[]) vals.toArray(new String[vals
+                                .size()]);
+                        for (int i = 0; i < val.length; i++) {
+                            if (resultOut != null) {
+                                actualBuffer.append("<actualHeader>"
+                                        + headerName + ": " + val[i]
+                                        + "</actualHeader>\n");
+                            }
+                        }
+                    }
+                    if (resultOut != null) {
+                        resultOut.append(expectedString);
+                        resultOut.append(actualBuffer.toString());
+                    }
+                }
+                return false;
+            }
+        }
+
+        /*
+         * Check to see if we're looking for unexpected headers. If we are,
+         * compare the values in the unexectedHeaders ArrayList against the
+         * headers from the server response. if the unexpected header is found,
+         * then return false.
+         */
+
+        if (!unexpectedHeaders.isEmpty()) {
+            boolean found = false;
+            String unExpHeader = null;
+            // Check if we got any unexpected headers
+
+            if (!copiedHeaders.isEmpty()) {
+                Iterator unexpectedIterator = unexpectedHeaders.keySet()
+                        .iterator();
+                while (unexpectedIterator.hasNext()) {
+                    found = false;
+                    String unexpectedFieldName = (String) unexpectedIterator
+                            .next();
+                    ArrayList unexpectedValues = (ArrayList) unexpectedHeaders
+                            .get(unexpectedFieldName);
+                    Iterator headersIterator = copiedHeaders.keySet()
+                            .iterator();
+
+                    while (headersIterator.hasNext()) {
+                        String headerFieldName = (String) headersIterator
+                                .next();
+                        ArrayList headerValues = (ArrayList) copiedHeaders
+                                .get(headerFieldName);
+
+                        // compare field names and values in an HTTP 1.x
+                        // compliant fashion
+                        if ((headerFieldName
+                                .equalsIgnoreCase(unexpectedFieldName))) {
+                            int hSize = headerValues.size();
+                            int eSize = unexpectedValues.size();
+                            int numberFound = 0;
+                            for (int i = 0; i < eSize; i++) {
+                                if (headerValues.contains(unexpectedValues
+                                        .get(i))) {
+                                    numberFound++;
+                                    if (headerValues.indexOf(headerFieldName) >= 0) {
+                                        headerValues.remove(headerValues
+                                                .indexOf(headerFieldName));
+                                    }
+                                }
+                            }
+                            if (numberFound == eSize) {
+                                found = true;
+                            }
+                        }
+                    }
+                    if (!found) {
+                        /*
+                         * Expected headers not found in server response. Break
+                         * the processing loop.
+                         */
+                        break;
+                    }
+                }
+            }
+
+            if (found) {
+                resultOut.append("\n Unexpected header received from server: "
+                        + unExpHeader);
+                return false;
+            }
+        }
+
+        if (responseMatch != null) {
+            // check if we got the string we wanted
+            if (expectResponseBody && responseBody == null) {
+                resultOut.append("\n ERROR: got no response, expecting "
+                        + responseMatch);
+                return false;
+            }
+            String responseBodyString = new String(responseBody);
+            if (responseBodyString.indexOf(responseMatch) < 0) {
+                resultOut.append("\n ERROR: expecting match on "
+                        + responseMatch);
+                resultOut.append("\n Received: \n" + responseBodyString);
+            }
+        }
+
+        if (!expectResponseBody && responseBody != null) {
+            resultOut
+                    .append("Received a response body from the server where none was expected");
+            return false;
+        }
+
+        // compare the body
+        if (goldenFile == null)
+            return true;
+
+        // Get the expected result from the "golden" file.
+        byte[] expResult = getExpectedResult();
+        String expResultS = (expResult == null) ? "" : new String(expResult);
+        // Compare the results and set the status
+        boolean cmp = true;
+
+        if (exactMatch) {
+            cmp = compare(responseBody, expResult);
+        } else {
+            cmp = compareWeak(responseBody, expResult);
+        }
+
+        if (cmp != testCondition) {
+
+            if (resultOut != null) {
+                expectedString = "<expectedBody>" + new String(expResult)
+                        + "</expectedBody>\n";
+                actualString = "<actualBody>"
+                        + (responseBody != null ? new String(responseBody)
+                                : "null") + "</actualBody>\n";
+                resultOut.append(expectedString);
+                resultOut.append(actualString);
+            }
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Replaces any |client.ip| and |client.host| parameter marks with the host
+     * and IP values of the host upon which Watchdog is running.
+     *
+     * @param request
+     *            An HTTP request.
+     */
+    String replaceMarkers(String req, Socket socket) {
+
+        final String CLIENT_IP = "client.ip";
+        final String CLIENT_HOME = "client.host";
+
+        if (localIP == null || localHost == null) {
+            InetAddress addr = socket.getLocalAddress();
+            localHost = addr.getHostName();
+            localIP = addr.getHostAddress();
+        }
+
+        if (req.indexOf('|') > -1) {
+            StringTokenizer tok = new StringTokenizer(request, "|");
+            StringBuffer sb = new StringBuffer(50);
+
+            while (tok.hasMoreElements()) {
+                String token = tok.nextToken();
+                if (token.equals(CLIENT_IP)) {
+                    sb.append(localIP);
+                } else if (token.equals(CLIENT_HOME)) {
+                    sb.append(localHost);
+                } else {
+                    sb.append(token);
+                }
+            }
+            return sb.toString();
+        } else {
+            return req;
+        }
+    }
+
+    /**
+     * <code>getExpectedResult</code> returns a byte array containing the
+     * content of the configured goldenfile
+     *
+     * @return goldenfile as a byte[]
+     * @exception IOException
+     *                if an error occurs
+     */
+    private byte[] getExpectedResult() throws IOException {
+        byte[] expResult = { 'N', 'O', ' ', 'G', 'O', 'L', 'D', 'E', 'N', 'F',
+                'I', 'L', 'E', ' ', 'F', 'O', 'U', 'N', 'D' };
+
+        try {
+            InputStream in = new BufferedInputStream(new FileInputStream(
+                    goldenFile));
+            return readBody(in);
+        } catch (Exception ex) {
+            System.out.println("Golden file not found: " + goldenFile);
+            return expResult;
+        }
+    }
+
+    /**
+     * <code>compare</code> compares the two byte arrays passed in to verify
+     * that the lengths of the arrays are equal, and that the content of the two
+     * arrays, byte for byte are equal.
+     *
+     * @param fromServer
+     *            a <code>byte[]</code> value
+     * @param fromGoldenFile
+     *            a <code>byte[]</code> value
+     * @return <code>boolean</code> true if equal, otherwise false
+     */
+    private boolean compare(byte[] fromServer, byte[] fromGoldenFile) {
+        if (fromServer == null || fromGoldenFile == null) {
+            return false;
+        }
+
+        /*
+         * Check to see that the respose and golden file lengths are equal. If
+         * they are not, dump the hex and don't bother comparing the bytes. If
+         * they are equal, iterate through the byte arrays and compare each
+         * byte. If the bytes don't match, dump the hex representation of the
+         * server response and the goldenfile and return false.
+         */
+        if (fromServer.length != fromGoldenFile.length) {
+            StringBuffer sb = new StringBuffer(50);
+            sb.append(" Response and golden files lengths do not match!\n");
+            sb.append(" Server response length: ");
+            sb.append(fromServer.length);
+            sb.append("\n Goldenfile length: ");
+            sb.append(fromGoldenFile.length);
+            resultOut.append(sb.toString());
+            sb = null;
+            // dump the hex representation of the byte arrays
+            dumpHex(fromServer, fromGoldenFile);
+
+            return false;
+        } else {
+
+            int i = 0;
+            int j = 0;
+
+            while ((i < fromServer.length) && (j < fromGoldenFile.length)) {
+                if (fromServer[i] != fromGoldenFile[j]) {
+                    resultOut.append("\n Error at position " + (i + 1));
+                    // dump the hex representation of the byte arrays
+                    dumpHex(fromServer, fromGoldenFile);
+
+                    return false;
+                }
+
+                i++;
+                j++;
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * <code>compareWeak</code> creates new Strings from the passed arrays and
+     * then uses a StringTokenizer to compare non-whitespace tokens.
+     *
+     * @param fromServer
+     *            a <code>byte[]</code> value
+     * @param fromGoldenFile
+     *            a <code>byte[]</code> value
+     * @return a <code>boolean</code> value
+     */
+    private boolean compareWeak(byte[] fromServer, byte[] fromGoldenFile) {
+        if (fromServer == null || fromGoldenFile == null) {
+            return false;
+        }
+
+        boolean status = true;
+
+        String server = new String(fromServer);
+        String golden = new String(fromGoldenFile);
+
+        StringTokenizer st1 = new StringTokenizer(server);
+
+        StringTokenizer st2 = new StringTokenizer(golden);
+
+        while (st1.hasMoreTokens() && st2.hasMoreTokens()) {
+            String tok1 = st1.nextToken();
+            String tok2 = st2.nextToken();
+
+            if (!tok1.equals(tok2)) {
+                resultOut.append("\t FAIL*** : Rtok1 = " + tok1 + ", Etok2 = "
+                        + tok2);
+                status = false;
+            }
+        }
+
+        if (st1.hasMoreTokens() || st2.hasMoreTokens()) {
+            status = false;
+        }
+
+        if (!status) {
+            StringBuffer sb = new StringBuffer(255);
+            sb
+                    .append("ERROR: Server's response and configured goldenfile do not match!\n");
+            sb.append("Response received from server:\n");
+            sb
+                    .append("---------------------------------------------------------\n");
+            sb.append(server);
+            sb.append("\nContent of Goldenfile:\n");
+            sb
+                    .append("---------------------------------------------------------\n");
+            sb.append(golden);
+            sb.append("\n");
+            resultOut.append(sb.toString());
+        }
+        return status;
+    }
+
+    /**
+     * <code>readBody</code> reads the body of the response from the
+     * InputStream.
+     *
+     * @param input
+     *            an <code>InputStream</code>
+     * @return a <code>byte[]</code> representation of the response
+     */
+    private byte[] readBody(InputStream input) {
+        StringBuffer sb = new StringBuffer(255);
+        while (true) {
+            try {
+                int ch = input.read();
+
+                if (ch < 0) {
+                    if (sb.length() == 0) {
+                        return (null);
+                    } else {
+                        break;
+                    }
+                }
+                sb.append((char) ch);
+
+            } catch (IOException ex) {
+                return null;
+            }
+        }
+        return sb.toString().getBytes();
+    }
+
+    /**
+     * <code>setHeaderDetails</code> Wrapper method for parseHeader. Allows easy
+     * addition of headers to the specified HashMap
+     *
+     * @param line
+     *            a <code>String</code> value
+     * @param headerMap
+     *            a <code>HashMap</code> value
+     * @param isRequest
+     *            a <code>boolean</code> indicating if the passed Header HashMap
+     *            is for request headers
+     */
+    void setHeaderDetails(String line, HashMap headerHash, boolean isRequest) {
+        StringTokenizer stk = new StringTokenizer(line, "##");
+
+        while (stk.hasMoreElements()) {
+            String presentHeader = stk.nextToken();
+            parseHeader(presentHeader, headerHash, isRequest);
+        }
+    }
+
+    /**
+     * <code>parseHeader</code> parses input headers in format of "key:value"
+     * The parsed header field-name will be used as a key in the passed HashMap
+     * object, and the values found will be stored in an ArrayList associated
+     * with the field-name key.
+     *
+     * @param line
+     *            String representation of an HTTP header line.
+     * @param headers
+     *            a<code>HashMap</code> to store key/value header objects.
+     * @param isRequest
+     *            set to true if the headers being processed are requestHeaders.
+     */
+    void parseHeader(String line, HashMap headerMap, boolean isRequest) {
+        // Parse the header name and value
+        int colon = line.indexOf(":");
+
+        if (colon < 0) {
+            resultOut
+                    .append("\n ERROR: Header is in incorrect format: " + line);
+            return;
+        }
+
+        String name = line.substring(0, colon).trim();
+        String value = line.substring(colon + 1).trim();
+
+        if ((cookieVector != null) && (name.equalsIgnoreCase("Set-Cookie"))) {
+            cookieVector.addElement(value);
+            /*
+             * if ( ( value.indexOf("JSESSIONID") > -1 ) ||
+             * (value.indexOf("jsessionid") > -1 ) ) { String sessionId=
+             * value.substring( value.indexOf("=")+1); if ( testSession != null
+             * ) { sessionHash.put( testSession, sessionId ); }
+             * System.out.println("Got Session-ID : " + sessionId ); }
+             */
+        }
+
+        // System.out.println("HEADER: " +name + " " + value);
+
+        ArrayList values = (ArrayList) headerMap.get(name);
+        if (values == null) {
+            values = new ArrayList();
+        }
+        // HACK
+        if (value.indexOf(',') > -1 && !isRequest
+                && !name.equalsIgnoreCase("Date")) {
+            StringTokenizer st = new StringTokenizer(value, ",");
+            while (st.hasMoreElements()) {
+                values.add(st.nextToken());
+            }
+        } else {
+            values.add(value);
+        }
+
+        headerMap.put(name, values);
+    }
+
+    /**
+     * <code>dumpHex</code> helper method to dump formatted hex output of the
+     * server response and the goldenfile.
+     *
+     * @param serverResponse
+     *            a <code>byte[]</code> value
+     * @param goldenFile
+     *            a <code>byte[]</code> value
+     */
+    private void dumpHex(byte[] serverResponse, byte[] goldenFile) {
+        StringBuffer outBuf = new StringBuffer(
+                (serverResponse.length + goldenFile.length) * 2);
+
+        String fromServerString = Hex.getHexDump(serverResponse, 0,
+                serverResponse.length, true);
+        String fromGoldenFileString = Hex.getHexDump(goldenFile, 0,
+                goldenFile.length, true);
+
+        outBuf
+                .append(" Hex dump of server response and goldenfile below.\n\n### RESPONSE FROM SERVER ###\n");
+        outBuf.append("----------------------------\n");
+        outBuf.append(fromServerString);
+        outBuf.append("\n\n### GOLDEN FILE ###\n");
+        outBuf.append("-------------------\n");
+        outBuf.append(fromGoldenFileString);
+        outBuf.append("\n\n### END OF DUMP ###\n");
+
+        resultOut.append(outBuf.toString());
+
+    }
+
+    /**
+     * <code>cloneHeaders</code> returns a "cloned" HashMap of the map passed
+     * in.
+     *
+     * @param map
+     *            a <code>HashMap</code> value
+     * @return a <code>HashMap</code> value
+     */
+    private HashMap cloneHeaders(HashMap map) {
+        HashMap dupMap = new HashMap();
+        Iterator iter = map.keySet().iterator();
+
+        while (iter.hasNext()) {
+            String key = new String((String) iter.next());
+            ArrayList origValues = (ArrayList) map.get(key);
+            ArrayList dupValues = new ArrayList();
+
+            String[] dupVal = (String[]) origValues
+                    .toArray(new String[origValues.size()]);
+            for (int i = 0; i < dupVal.length; i++) {
+                dupValues.add(new String(dupVal[i]));
+            }
+
+            dupMap.put(key, dupValues);
+        }
+        return dupMap;
+    }
+
+}
diff --git a/res/META-INF/jasper.jar/services/javax.servlet.ServletContainerInitializer b/res/META-INF/jasper.jar/services/javax.servlet.ServletContainerInitializer
index 2eb8f6d..33a59da 100644
--- a/res/META-INF/jasper.jar/services/javax.servlet.ServletContainerInitializer
+++ b/res/META-INF/jasper.jar/services/javax.servlet.ServletContainerInitializer
@@ -1,16 +1 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
 org.apache.jasper.servlet.JasperInitializer
\ No newline at end of file
diff --git a/res/META-INF/tomcat-websocket.jar/services/javax.servlet.ServletContainerInitializer b/res/META-INF/tomcat-websocket.jar/services/javax.servlet.ServletContainerInitializer
index c850c01..85ee1c0 100644
--- a/res/META-INF/tomcat-websocket.jar/services/javax.servlet.ServletContainerInitializer
+++ b/res/META-INF/tomcat-websocket.jar/services/javax.servlet.ServletContainerInitializer
@@ -1,16 +1 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
 org.apache.tomcat.websocket.server.WsSci
\ No newline at end of file
diff --git a/res/META-INF/tomcat-websocket.jar/services/javax.websocket.ContainerProvider b/res/META-INF/tomcat-websocket.jar/services/javax.websocket.ContainerProvider
index abdaee2..91c8204 100644
--- a/res/META-INF/tomcat-websocket.jar/services/javax.websocket.ContainerProvider
+++ b/res/META-INF/tomcat-websocket.jar/services/javax.websocket.ContainerProvider
@@ -1,16 +1 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
 org.apache.tomcat.websocket.WsContainerProvider
\ No newline at end of file
diff --git a/res/META-INF/tomcat-websocket.jar/services/javax.websocket.server.ServerEndpointConfig$Configurator b/res/META-INF/tomcat-websocket.jar/services/javax.websocket.server.ServerEndpointConfig$Configurator
index 6453734..38046d2 100644
--- a/res/META-INF/tomcat-websocket.jar/services/javax.websocket.server.ServerEndpointConfig$Configurator
+++ b/res/META-INF/tomcat-websocket.jar/services/javax.websocket.server.ServerEndpointConfig$Configurator
@@ -1,16 +1 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
 org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator
\ No newline at end of file
diff --git a/res/checkstyle/org-import-control.xml b/res/checkstyle/org-import-control.xml
index b872bb2..8131963 100644
--- a/res/checkstyle/org-import-control.xml
+++ b/res/checkstyle/org-import-control.xml
@@ -113,7 +113,6 @@
   <subpackage name="naming">
     <allow pkg="javax.mail"/>
     <allow pkg="javax.wsdl"/>
-    <allow pkg="org.apache.juli"/>
     <allow pkg="org.apache.naming"/>
     <allow class="org.apache.tomcat.util.buf.UDecoder"/>
     <allow class="org.apache.tomcat.util.buf.UEncoder"/>
diff --git a/res/ide-support/eclipse/eclipse.classpath b/res/ide-support/eclipse/eclipse.classpath
index 9ea2e91..c7ec29e 100644
--- a/res/ide-support/eclipse/eclipse.classpath
+++ b/res/ide-support/eclipse/eclipse.classpath
@@ -19,7 +19,7 @@
     <classpathentry kind="src" path="java"/>
     <classpathentry kind="src" path="test"/>
     <classpathentry kind="src" path="webapps/examples/WEB-INF/classes"/>
-    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
+    <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
     <classpathentry kind="con" path="org.eclipse.jdt.junit.JUNIT_CONTAINER/4"/>
     <classpathentry kind="var" path="ANT_HOME/lib/ant.jar"/>
     <classpathentry kind="var" path="TOMCAT_LIBS_BASE/jaxrpc-1.1-rc4/geronimo-spec-jaxrpc-1.1-rc4.jar"/>
diff --git a/res/ide-support/eclipse/eclipse.project b/res/ide-support/eclipse/eclipse.project
index 75504fb..d480797 100644
--- a/res/ide-support/eclipse/eclipse.project
+++ b/res/ide-support/eclipse/eclipse.project
@@ -16,7 +16,7 @@
   limitations under the License.
 -->
 <projectDescription>
-    <name>tomcat-9.0.x</name>
+    <name>tomcat-8.0.x</name>
     <comment></comment>
     <projects>
     </projects>
diff --git a/res/ide-support/eclipse/org.eclipse.jdt.core.prefs.properties b/res/ide-support/eclipse/org.eclipse.jdt.core.prefs.properties
index c7fcfab..bdbfe41 100644
--- a/res/ide-support/eclipse/org.eclipse.jdt.core.prefs.properties
+++ b/res/ide-support/eclipse/org.eclipse.jdt.core.prefs.properties
@@ -15,6 +15,6 @@
 # limitations under the License.
 # -----------------------------------------------------------------------------
 eclipse.preferences.version=1
-org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
-org.eclipse.jdt.core.compiler.compliance=1.8
-org.eclipse.jdt.core.compiler.source=1.8
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.7
+org.eclipse.jdt.core.compiler.compliance=1.7
+org.eclipse.jdt.core.compiler.source=1.7
diff --git a/res/ide-support/eclipse/start-tomcat.launch b/res/ide-support/eclipse/start-tomcat.launch
index 49cf8bf..a4d4d86 100644
--- a/res/ide-support/eclipse/start-tomcat.launch
+++ b/res/ide-support/eclipse/start-tomcat.launch
@@ -17,13 +17,13 @@
 -->
 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/tomcat-9.0.x/java/org/apache/catalina/startup/Bootstrap.java"/>
+<listEntry value="/tomcat-8.0.x/java/org/apache/catalina/startup/Bootstrap.java"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.apache.catalina.startup.Bootstrap"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="start"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="tomcat-9.0.x"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dcatalina.home=${project_loc:/tomcat-9.0.x/java/org/apache/catalina/startup/Bootstrap.java}/output/build"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="tomcat-8.0.x"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dcatalina.home=${project_loc:/tomcat-8.0.x/java/org/apache/catalina/startup/Bootstrap.java}/output/build"/>
 </launchConfiguration>
diff --git a/res/ide-support/eclipse/stop-tomcat.launch b/res/ide-support/eclipse/stop-tomcat.launch
index 5d4ee25..4ead818 100644
--- a/res/ide-support/eclipse/stop-tomcat.launch
+++ b/res/ide-support/eclipse/stop-tomcat.launch
@@ -17,13 +17,13 @@
 -->
 <launchConfiguration type="org.eclipse.jdt.launching.localJavaApplication">
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_PATHS">
-<listEntry value="/tomcat-9.0.x/java/org/apache/catalina/startup/Bootstrap.java"/>
+<listEntry value="/tomcat-8.0.x/java/org/apache/catalina/startup/Bootstrap.java"/>
 </listAttribute>
 <listAttribute key="org.eclipse.debug.core.MAPPED_RESOURCE_TYPES">
 <listEntry value="1"/>
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="org.apache.catalina.startup.Bootstrap"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="stop"/>
-<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="tomcat-9.0.x"/>
-<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dcatalina.home=${project_loc:/tomcat-9.0.x/java/org/apache/catalina/startup/Bootstrap.java}/output/build"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="tomcat-8.0.x"/>
+<stringAttribute key="org.eclipse.jdt.launching.VM_ARGUMENTS" value="-Dcatalina.home=${project_loc:/tomcat-8.0.x/java/org/apache/catalina/startup/Bootstrap.java}/output/build"/>
 </launchConfiguration>
diff --git a/res/rat/rat-excludes.txt b/res/rat/rat-excludes.txt
index 429613c..b166558 100644
--- a/res/rat/rat-excludes.txt
+++ b/res/rat/rat-excludes.txt
@@ -25,28 +25,13 @@
 
   - *.manifest JAR manifest files cannot contain license
 
-  - package-list and script.js files in API documentation (javadoc) are
-    generated
+  - package-list files in API documentation (javadoc) are generated
 
   - other trivial test files, such as textual files containing only "OK' string,
-    are also excluded.
+  are also excluded.
 
   - JSON files (RFC7159) are data and cannot contain comments
 
-  - local build configuration files
-
-  - *.mdl are Rational Rose data files and cannot contain comments
-
-  - file fragments that are combined during the build process and therefore can
-    not contain a license header in every fragment
-
-  - JavaEE XML schemas that are CDDL licensed
-
-  - Checkstyle configuration file that defines how to check for the presence of
-    ALv2 headers
-
-  - files used simply to ensure directories are not empty
-
 output/build/webapps/docs/*.html
 output/build/webapps/docs/appdev/*.html
 output/build/webapps/docs/architecture/*.html
@@ -63,10 +48,6 @@
 
 output/embed/*.md5
 output/extras/*.md5
-output/release/v9.0.0-dev/bin/*.md5
-output/release/v9.0.0-dev/bin/embed/*.md5
-output/release/v9.0.0-dev/bin/extras/*.md5
-output/release/v9.0.0-dev/src/*.md5
 
 modules/jdbc-pool/resources/MANIFEST.MF
 output/dist/src/modules/jdbc-pool/resources/MANIFEST.MF
@@ -76,98 +57,15 @@
 res/META-INF/*.manifest
 
 output/dist/webapps/docs/api/package-list
-output/dist/webapps/docs/api/script.js
 output/dist/webapps/docs/elapi/package-list
-output/dist/webapps/docs/elapi/script.js
 output/dist/webapps/docs/jspapi/package-list
-output/dist/webapps/docs/jspapi/script.js
 output/dist/webapps/docs/servletapi/package-list
-output/dist/webapps/docs/servletapi/script.js
-output/dist/webapps/docs/websocketapi/package-list
-output/dist/webapps/docs/websocketapi/script.js
 
 output/dist/src/test/webapp/bug53257/*.txt
-output/dist/src/test/webapp/bug53257/foo bar/foobar.txt
 output/dist/src/test/webapp-fragments/WEB-INF/classes/*.txt
-output/dist/src/test/webresources/dir1/d1/d1-f1.txt
-output/dist/src/test/webresources/dir1/d2/d2-f1.txt
-output/dist/src/test/webresources/dir1/*.txt
 test/webapp/bug53257/*.txt
-test/webapp/bug53257/foo bar/foobar.txt
 test/webapp-fragments/WEB-INF/classes/*.txt
-test/webresources/dir1/d1/d1-f1.txt
-test/webresources/dir1/d2/d2-f1.txt
-test/webresources/dir1/*.txt
 
 webapps/examples/WEB-INF/classes/websocket/echo/servers.json
 output/build/webapps/examples/WEB-INF/classes/websocket/echo/servers.json
-output/dist/src/webapps/examples/WEB-INF/classes/websocket/echo/servers.json
 output/dist/webapps/examples/WEB-INF/classes/websocket/echo/servers.json
-
-build.properties
-
-output/build/webapps/docs/architecture/requestProcess/roseModel.mdl
-output/dist/src/webapps/docs/architecture/requestProcess/roseModel.mdl
-output/dist/webapps/docs/architecture/requestProcess/roseModel.mdl
-webapps/docs/architecture/requestProcess/roseModel.mdl
-
-output/dist/confinstall/tomcat-users_2.xml
-output/dist/src/res/confinstall/tomcat-users_2.xml
-res/confinstall/tomcat-users_2.xml
-
-java/javax/servlet/resources/javaee_5.xsd
-java/javax/servlet/resources/javaee_6.xsd
-java/javax/servlet/resources/javaee_7.xsd
-java/javax/servlet/resources/javaee_web_services_1_2.xsd
-java/javax/servlet/resources/javaee_web_services_1_3.xsd
-java/javax/servlet/resources/javaee_web_services_1_4.xsd
-java/javax/servlet/resources/javaee_web_services_client_1_2.xsd
-java/javax/servlet/resources/javaee_web_services_client_1_3.xsd
-java/javax/servlet/resources/javaee_web_services_client_1_4.xsd
-java/javax/servlet/resources/jsp_2_2.xsd
-java/javax/servlet/resources/jsp_2_3.xsd
-java/javax/servlet/resources/web-app_3_0.xsd
-java/javax/servlet/resources/web-app_3_1.xsd
-java/javax/servlet/resources/web-common_3_0.xsd
-java/javax/servlet/resources/web-common_3_1.xsd
-java/javax/servlet/resources/web-fragment_3_0.xsd
-java/javax/servlet/resources/web-fragment_3_1.xsd
-output/classes/javax/servlet/resources/javaee_5.xsd
-output/classes/javax/servlet/resources/javaee_6.xsd
-output/classes/javax/servlet/resources/javaee_7.xsd
-output/classes/javax/servlet/resources/javaee_web_services_1_2.xsd
-output/classes/javax/servlet/resources/javaee_web_services_1_3.xsd
-output/classes/javax/servlet/resources/javaee_web_services_1_4.xsd
-output/classes/javax/servlet/resources/javaee_web_services_client_1_2.xsd
-output/classes/javax/servlet/resources/javaee_web_services_client_1_3.xsd
-output/classes/javax/servlet/resources/javaee_web_services_client_1_4.xsd
-output/classes/javax/servlet/resources/jsp_2_2.xsd
-output/classes/javax/servlet/resources/jsp_2_3.xsd
-output/classes/javax/servlet/resources/web-app_3_0.xsd
-output/classes/javax/servlet/resources/web-app_3_1.xsd
-output/classes/javax/servlet/resources/web-common_3_0.xsd
-output/classes/javax/servlet/resources/web-common_3_1.xsd
-output/classes/javax/servlet/resources/web-fragment_3_0.xsd
-output/classes/javax/servlet/resources/web-fragment_3_1.xsd
-output/dist/src/java/javax/servlet/resources/javaee_5.xsd
-output/dist/src/java/javax/servlet/resources/javaee_6.xsd
-output/dist/src/java/javax/servlet/resources/javaee_7.xsd
-output/dist/src/java/javax/servlet/resources/javaee_web_services_1_2.xsd
-output/dist/src/java/javax/servlet/resources/javaee_web_services_1_3.xsd
-output/dist/src/java/javax/servlet/resources/javaee_web_services_1_4.xsd
-output/dist/src/java/javax/servlet/resources/javaee_web_services_client_1_2.xsd
-output/dist/src/java/javax/servlet/resources/javaee_web_services_client_1_3.xsd
-output/dist/src/java/javax/servlet/resources/javaee_web_services_client_1_4.xsd
-output/dist/src/java/javax/servlet/resources/jsp_2_2.xsd
-output/dist/src/java/javax/servlet/resources/jsp_2_3.xsd
-output/dist/src/java/javax/servlet/resources/web-app_3_0.xsd
-output/dist/src/java/javax/servlet/resources/web-app_3_1.xsd
-output/dist/src/java/javax/servlet/resources/web-common_3_0.xsd
-output/dist/src/java/javax/servlet/resources/web-common_3_1.xsd
-output/dist/src/java/javax/servlet/resources/web-fragment_3_0.xsd
-output/dist/src/java/javax/servlet/resources/web-fragment_3_1.xsd
-
-output/dist/src/res/checkstyle/header-al2.txt
-res/checkstyle/header-al2.txt
-
-output/dist/temp/safeToDelete.tmp
diff --git a/test/META-INF/services/javax.servlet.ServletContainerInitializer b/test/META-INF/services/javax.servlet.ServletContainerInitializer
index 6cb967c..a24853a 100644
--- a/test/META-INF/services/javax.servlet.ServletContainerInitializer
+++ b/test/META-INF/services/javax.servlet.ServletContainerInitializer
@@ -1,17 +1,2 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-
 # SCIs that should be added when running tests
 org.apache.jasper.servlet.JasperInitializer
\ No newline at end of file
diff --git a/test/org/apache/catalina/comet/TestCometProcessor.java b/test/org/apache/catalina/comet/TestCometProcessor.java
new file mode 100644
index 0000000..1fc824d
--- /dev/null
+++ b/test/org/apache/catalina/comet/TestCometProcessor.java
@@ -0,0 +1,672 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You 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.catalina.comet;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+
+import javax.net.SocketFactory;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.servlet.http.HttpSession;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import org.junit.Assert;
+import org.junit.Assume;
+import org.junit.Test;
+
+import org.apache.catalina.Context;
+import org.apache.catalina.Wrapper;
+import org.apache.catalina.comet.CometEvent.EventType;
+import org.apache.catalina.connector.CometEventImpl;
+import org.apache.catalina.connector.Request;
+import org.apache.catalina.connector.Response;
+import org.apache.catalina.startup.Tomcat;
+import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.catalina.valves.TesterAccessLogValve;
+import org.apache.catalina.valves.ValveBase;
+
+public class TestCometProcessor extends TomcatBaseTest {
+
+    @Test
+    public void testAsyncClose() throws Exception {
+        Assume.assumeTrue(
+                "This test is skipped, because this connector does not support Comet.",
+                isCometSupported());
+
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+        Context root = tomcat.addContext("", TEMP_DIR);
+        Tomcat.addServlet(root, "comet", new SimpleCometServlet());
+        root.addServletMapping("/comet", "comet");
+        Tomcat.addServlet(root, "hello", new HelloWorldServlet());
+        root.addServletMapping("/hello", "hello");
+        root.getPipeline().addValve(new AsyncCometCloseValve());
+        tomcat.getConnector().setProperty("connectionTimeout", "5000");
+        tomcat.start();
+
+        // Create connection to Comet servlet
+        final Socket socket =
+            SocketFactory.getDefault().createSocket("localhost", getPort());
+        socket.setSoTimeout(5000);
+
+        final OutputStream os = socket.getOutputStream();
+        String requestLine = "POST http://localhost:" + getPort() +
+                "/comet HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("transfer-encoding: chunked\r\n".getBytes());
+        os.write("\r\n".getBytes());
+
+        InputStream is = socket.getInputStream();
+        ResponseReaderThread readThread = new ResponseReaderThread(is);
+        readThread.start();
+
+        // Wait for the comet request/response to finish
+        int count = 0;
+        while (count < 10 && !readThread.getResponse().endsWith("0\r\n\r\n")) {
+            Thread.sleep(500);
+            count++;
+        }
+
+        if (count == 10) {
+            fail("Comet request did not complete");
+        }
+
+        // Send a standard HTTP request on the same connection
+        requestLine = "GET http://localhost:" + getPort() +
+                "/hello HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("\r\n".getBytes());
+
+        // Check for the expected response
+        count = 0;
+        while (count < 10 && !readThread.getResponse().contains(
+                HelloWorldServlet.RESPONSE_TEXT)) {
+            Thread.sleep(500);
+            count++;
+        }
+
+        if (count == 10) {
+            fail("Non-comet request did not complete");
+        }
+
+        readThread.join();
+        os.close();
+        is.close();
+    }
+
+    @Test
+    public void testSyncClose() throws Exception {
+        Assume.assumeTrue(
+                "This test is skipped, because this connector does not support Comet.",
+                isCometSupported());
+
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+        Context root = tomcat.addContext("", TEMP_DIR);
+        Tomcat.addServlet(root, "comet", new CometCloseServlet());
+        root.addServletMapping("/comet", "comet");
+        Tomcat.addServlet(root, "hello", new HelloWorldServlet());
+        root.addServletMapping("/hello", "hello");
+        tomcat.getConnector().setProperty("connectionTimeout", "5000");
+        tomcat.start();
+
+        // Create connection to Comet servlet
+        final Socket socket =
+            SocketFactory.getDefault().createSocket("localhost", getPort());
+        socket.setSoTimeout(5000);
+
+        final OutputStream os = socket.getOutputStream();
+        String requestLine = "POST http://localhost:" + getPort() +
+                "/comet HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("transfer-encoding: chunked\r\n".getBytes());
+        os.write("\r\n".getBytes());
+        // Don't send any data
+        os.write("0\r\n\r\n".getBytes());
+
+        InputStream is = socket.getInputStream();
+        ResponseReaderThread readThread = new ResponseReaderThread(is);
+        readThread.start();
+
+        // Wait for the comet request/response to finish
+        int count = 0;
+        while (count < 10 && !readThread.getResponse().endsWith("0\r\n\r\n")) {
+            Thread.sleep(500);
+            count++;
+        }
+
+        Assert.assertTrue(readThread.getResponse().contains("2\r\nOK"));
+
+        if (count == 10) {
+            fail("Comet request did not complete");
+        }
+
+        // Send a standard HTTP request on the same connection
+        requestLine = "GET http://localhost:" + getPort() +
+                "/hello HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("connection: close\r\n".getBytes());
+        os.write("\r\n".getBytes());
+
+        // Check for the expected response
+        count = 0;
+        while (count < 10 && !readThread.getResponse().contains(
+                HelloWorldServlet.RESPONSE_TEXT)) {
+            Thread.sleep(500);
+            count++;
+        }
+
+        if (count == 10) {
+            fail("Non-comet request did not complete");
+        }
+
+        readThread.join();
+        os.close();
+        is.close();
+    }
+
+    @Test
+    public void testConnectionClose() throws Exception {
+        Assume.assumeTrue(
+                "This test is skipped, because this connector does not support Comet.",
+                isCometSupported());
+
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+        Context root = tomcat.addContext("", TEMP_DIR);
+        Tomcat.addServlet(root, "comet", new ConnectionCloseServlet());
+        root.addServletMapping("/comet", "comet");
+        Tomcat.addServlet(root, "hello", new HelloWorldServlet());
+        root.addServletMapping("/hello", "hello");
+        tomcat.getConnector().setProperty("connectionTimeout", "5000");
+        tomcat.start();
+
+        // Create connection to Comet servlet
+        final Socket socket =
+            SocketFactory.getDefault().createSocket("localhost", getPort());
+        socket.setSoTimeout(5000);
+
+        final OutputStream os = socket.getOutputStream();
+        String requestLine = "POST http://localhost:" + getPort() +
+                "/comet HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("transfer-encoding: chunked\r\n".getBytes());
+        os.write("\r\n".getBytes());
+        // Don't send any data
+        os.write("0\r\n\r\n".getBytes());
+
+        InputStream is = socket.getInputStream();
+        ResponseReaderThread readThread = new ResponseReaderThread(is);
+        readThread.start();
+
+        // Wait for the comet request/response to finish
+        int count = 0;
+        while (count < 10 && !readThread.getResponse().endsWith("OK")) {
+            Thread.sleep(500);
+            count++;
+        }
+
+        if (count == 10) {
+            fail("Comet request did not complete");
+        }
+
+        // Read thread should have terminated cleanly when the server closed the
+        // socket
+        Assert.assertFalse(readThread.isAlive());
+        Assert.assertNull(readThread.getException());
+
+        os.close();
+        is.close();
+    }
+
+    @Test
+    public void testSimpleCometClient() throws Exception {
+        doSimpleCometTest(null);
+    }
+
+    @Test
+    public void testSimpleCometClientBeginFail() throws Exception {
+        doSimpleCometTest(SimpleCometServlet.FAIL_ON_BEGIN);
+    }
+
+    @Test
+    public void testSimpleCometClientReadFail() throws Exception {
+        doSimpleCometTest(SimpleCometServlet.FAIL_ON_READ);
+    }
+
+    @Test
+    public void testSimpleCometClientEndFail() throws Exception {
+        doSimpleCometTest(SimpleCometServlet.FAIL_ON_END);
+    }
+
+    private void doSimpleCometTest(String initParam) throws Exception {
+        Assume.assumeTrue(
+                "This test is skipped, because this connector does not support Comet.",
+                isCometSupported());
+
+        // Setup Tomcat instance
+        Tomcat tomcat = getTomcatInstance();
+        Context root = tomcat.addContext("", TEMP_DIR);
+        Wrapper w = Tomcat.addServlet(root, "comet", new SimpleCometServlet());
+        if (initParam != null) {
+            w.addInitParameter(initParam, "true");
+        }
+        root.addServletMapping("/", "comet");
+
+        TesterAccessLogValve alv = new TesterAccessLogValve();
+        root.getPipeline().addValve(alv);
+
+        tomcat.start();
+
+        // Create connection to Comet servlet
+        final Socket socket =
+            SocketFactory.getDefault().createSocket("localhost", getPort());
+        socket.setSoTimeout(60000);
+
+        final OutputStream os = socket.getOutputStream();
+        String requestLine = "POST http://localhost:" + getPort() +
+                "/ HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("transfer-encoding: chunked\r\n".getBytes());
+        os.write("\r\n".getBytes());
+
+        PingWriterThread writeThread = new PingWriterThread(4, os);
+        writeThread.start();
+
+        socket.setSoTimeout(25000);
+        InputStream is = socket.getInputStream();
+        ResponseReaderThread readThread = new ResponseReaderThread(is);
+        readThread.start();
+        readThread.join();
+        os.close();
+        is.close();
+
+        String[] response = readThread.getResponse().split("\r\n");
+        if (initParam == null) {
+            // Normal response expected
+            // Validate response
+            assertEquals("HTTP/1.1 200 OK", response[0]);
+            assertEquals("Server: Apache-Coyote/1.1", response[1]);
+            assertTrue(response[2].startsWith("Set-Cookie: JSESSIONID="));
+            assertEquals("Content-Type: text/plain;charset=ISO-8859-1", response[3]);
+            assertEquals("Transfer-Encoding: chunked", response[4]);
+            assertTrue(response[5].startsWith("Date: "));
+            assertEquals("", response[6]);
+            assertEquals("7", response[7]);
+            assertEquals("BEGIN", response[8]);
+            assertEquals("", response[9]);
+            assertEquals("17", response[10]);
+            assertEquals("Client: READ: 4 bytes", response[11]);
+            assertEquals("", response[12]);
+            assertEquals("17", response[13]);
+            assertEquals("Client: READ: 4 bytes", response[14]);
+            assertEquals("", response[15]);
+            assertEquals("17", response[16]);
+            assertEquals("Client: READ: 4 bytes", response[17]);
+            assertEquals("", response[18]);
+            assertEquals("17", response[19]);
+            assertEquals("Client: READ: 4 bytes", response[20]);
+            assertEquals("", response[21]);
+            assertEquals("d", response[22]);
+            assertEquals("Client: END", response[23]);
+            assertEquals("", response[24]);
+            assertEquals("0", response[25]);
+            // Expect 26 lines
+            assertEquals(26, response.length);
+        } else {
+            // Failure expected only expected for the fail on begin
+            // Failure at any later stage and the response headers (including
+            // the 200 response code will already have been sent to the client
+            if (SimpleCometServlet.FAIL_ON_BEGIN.equals(initParam)) {
+                assertEquals("500", getStatusCode(response[0]));
+                alv.validateAccessLog(1, 500, 0, 1000);
+            } else {
+                assertEquals("HTTP/1.1 200 OK", response[0]);
+                alv.validateAccessLog(1, 200, 0, 5000);
+            }
+
+        }
+    }
+
+    /*
+     * Tests if the Comet connection is closed if the Tomcat connector is
+     * stopped.
+     */
+    @Test
+    public void testCometConnectorStop() throws Exception {
+        Assume.assumeTrue(
+                "This test is skipped, because this connector does not support Comet.",
+                isCometSupported());
+
+        // Setup Tomcat instance
+        SimpleCometServlet servlet = new SimpleCometServlet();
+        Tomcat tomcat = getTomcatInstance();
+        Context root = tomcat.addContext("", TEMP_DIR);
+        Tomcat.addServlet(root, "comet", servlet);
+        root.addServletMapping("/", "comet");
+        tomcat.start();
+
+        // Create connection to Comet servlet
+        final Socket socket =
+            SocketFactory.getDefault().createSocket("localhost", getPort());
+        socket.setSoTimeout(10000);
+
+        final OutputStream os = socket.getOutputStream();
+        String requestLine = "POST http://localhost:" + getPort() +
+                "/ HTTP/1.1\r\n";
+        os.write(requestLine.getBytes());
+        os.write("transfer-encoding: chunked\r\n".getBytes());
+        os.write("\r\n".getBytes());
+
+        PingWriterThread writeThread = new PingWriterThread(100, os);
+        writeThread.start();
+
+        InputStream is = socket.getInputStream();
+        ResponseReaderThread readThread = new ResponseReaderThread(is);
+        readThread.start();
+
+        // Allow the first couple of PING messages to be written
+        Thread.sleep(3000);
+
+        tomcat.getConnector().stop();
+
+        // Wait for the read and write threads to stop
+        readThread.join(5000);
+        writeThread.join(5000);
+
+        // Destroy the connector once the executor has sent the end event
+        tomcat.getConnector().destroy();
+
+        String[] response = readThread.getResponse().split("\r\n");
+        String lastMessage = "";
+        String lastResponseLine = "";
+        for (int i = response.length; --i >= 0;) {
+            lastMessage = response[i];
+            if (lastMessage.startsWith("Client:")) {
+                break;
+            }
+        }
+        for (int i = response.length; --i >= 0;) {
+            lastResponseLine = response[i];
+            if (lastResponseLine.length() > 0) {
+                break;
+            }
+        }
+        StringBuilder status = new StringBuilder();
+        // Expected, but is not 100% reliable:
+        // WriteThread exception: java.net.SocketException
+        // ReaderThread exception: null
+        // Last message: [Client: END]
+        // Last response line: [0] (empty chunk)
+        // Last comet event: [END]
+        // END event occurred: [true]
+        status.append("Status:");
+        status.append("\nWriterThread exception: " + writeThread.getException());
+        status.append("\nReaderThread exception: " + readThread.getException());
+        status.append("\nLast message: [" + lastMessage + "]");
+        status.append("\nLast response line: [" + lastResponseLine + "]");
+        status.append("\nLast comet event: [" + servlet.getLastEvent() + "]");
+        status.append("\nEND event occurred: [" + servlet.getEndEventOccurred() + "]");
+        if (writeThread.getException() == null
+                || !lastMessage.contains("Client: END")
+                || !EventType.END.equals(servlet.getLastEvent())) {
+            log.error(status);
+        } else {
+            log.info(status);
+        }
+        assertTrue("Comet END event not received", servlet.getEndEventOccurred());
+        assertTrue("Comet END event not last event received",
+                EventType.END.equals(servlet.getLastEvent()));
+    }
+
+    private boolean isCometSupported() {
+        String protocol =
+            getTomcatInstance().getConnector().getProtocolHandlerClassName();
+        return (protocol.contains("Nio") || protocol.contains("Apr"));
+    }
+
+    private static class SimpleCometServlet extends HttpServlet
+            implements CometProcessor {
+
+        private static final long serialVersionUID = 1L;
+
+        public static final String FAIL_ON_BEGIN = "failOnBegin";
+        public static final String FAIL_ON_READ = "failOnRead";
+        public static final String FAIL_ON_END = "failOnEnd";
+
+        private boolean failOnBegin = false;
+        private boolean failOnRead = false;
+        private boolean failOnEnd = false;
+
+        private volatile EventType lastEvent;
+
+        private volatile boolean endEventOccurred = false;
+
+        public EventType getLastEvent() {
+            return lastEvent;
+        }
+
+        public boolean getEndEventOccurred() {
+            return endEventOccurred;
+        }
+
+        @Override
+        public void init() throws ServletException {
+            failOnBegin = Boolean.valueOf(getServletConfig().getInitParameter(
+                    FAIL_ON_BEGIN)).booleanValue();
+            failOnRead = Boolean.valueOf(getServletConfig().getInitParameter(
+                    FAIL_ON_READ)).booleanValue();
+            failOnEnd = Boolean.valueOf(getServletConfig().getInitParameter(
+                    FAIL_ON_END)).booleanValue();
+        }
+
+
+        @Override
+        public void event(CometEvent event) throws IOException,
+                ServletException {
+
+            HttpServletRequest request = event.getHttpServletRequest();
+            HttpServletResponse response = event.getHttpServletResponse();
+
+            HttpSession session = request.getSession(true);
+            session.setMaxInactiveInterval(30);
+
+            lastEvent = event.getEventType();
+
+            if (event.getEventType() == EventType.BEGIN) {
+                if (failOnBegin) {
+                    throw new IOException("Fail on begin");
+                }
+                response.setContentType("text/plain");
+                response.getWriter().print("BEGIN" + "\r\n");
+            } else if (event.getEventType() == EventType.READ) {
+                if (failOnRead) {
+                    throw new IOException("Fail on read");
+                }
+                InputStream is = request.getInputStream();
+                int count = 0;
+                while (is.available() > 0) {
+                    is.read();
+                    count ++;
+                }
+                String msg = "READ: " + count + " bytes";
+                response.getWriter().print("Client: " + msg + "\r\n");
+            } else if (event.getEventType() == EventType.END) {
+                endEventOccurred = true;
+                if (failOnEnd) {
+                    throw new IOException("Fail on end");
+                }
+                String msg = "END";
+                response.getWriter().print("Client: " + msg + "\r\n");
+                event.close();
+            } else {
+                String msg = event.getEventType() + ":" + event.getEventSubType() + "\r\n";
+                System.out.print(msg);
+                response.getWriter().print(msg);
+                event.close();
+            }
+            response.getWriter().flush();
+        }
+    }
+
+    private static class CometCloseServlet extends HttpServlet
+            implements CometProcessor {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public void event(CometEvent event) throws IOException,
+                ServletException {
+            HttpServletResponse response = event.getHttpServletResponse();
+            response.setContentType("text/plain");
+            // Force a chunked response since that is what the test client
+            // expects
+            response.flushBuffer();
+            response.getWriter().print("OK");
+            event.close();
+        }
+
+    }
+
+    private static class ConnectionCloseServlet extends HttpServlet
+            implements CometProcessor {
+
+        private static final long serialVersionUID = 1L;
+
+        @Override
+        public void event(CometEvent event) throws IOException,
+                ServletException {
+            HttpServletResponse response = event.getHttpServletResponse();
+            response.setContentType("text/plain");
+            // Disable keep-alive
+            response.setHeader("Connection", "close");
+            response.flushBuffer();
+            response.getWriter().print("OK");
+            event.close();
+        }
+    }
+
+    private static class PingWriterThread extends Thread {
+
+        private final int pingCount;
+        private final OutputStream os;
+        private volatile Exception e = null;
+
+        public PingWriterThread(int pingCount, OutputStream os) {
+            this.pingCount = pingCount;
+            this.os = os;
+        }
+
+        public Exception getException() {
+            return e;
+        }
+
+        @Override
+        public void run() {
+            try {
+                for (int i = 0; i < pingCount; i++) {
+                    os.write("4\r\n".getBytes());
+                    os.write("PING\r\n".getBytes());
+                    os.flush();
+                    Thread.sleep(1000);
+                }
+                os.write("0\r\n".getBytes());
+                os.write("\r\n".getBytes());
+            } catch (Exception e) {
+                this.e = e;
+            }
+        }
+    }
+
+    private static class ResponseReaderThread extends Thread {
+
+        private final InputStream is;
+        private StringBuilder response = new StringBuilder();
+
+        private volatile Exception e = null;
+
+        public ResponseReaderThread(InputStream is) {
+            this.is = is;
+        }
+
+        public Exception getException() {
+            return e;
+        }
+
+        public String getResponse() {
+            return response.toString();
+        }
+
+        @Override
+        public void run() {
+            try {
+                int c = is.read();
+                while (c > -1) {
+                    response.append((char) c);
+                    c = is.read();
+                }
+            } catch (Exception e) {
+                this.e = e;
+            }
+        }
+    }
+
+    private static class AsyncCometCloseValve extends ValveBase {
+
+        @Override
+        public void invoke(Request request, Response response)
+                throws IOException, ServletException {
+
+            CometEventImpl event = new CometEventImpl(request, response);
+
+            getNext().invoke(request, response);
+
+            if (request.isComet()) {
+                Thread t = new AsyncCometCloseThread(event);
+                t.start();
+            }
+        }
+    }
+
+    private static class AsyncCometCloseThread extends Thread {
+
+        private final CometEvent event;
+
+        public AsyncCometCloseThread(CometEvent event) {
+            this.event = event;
+        }
+
+        @Override
+        public void run() {
+            try {
+                Thread.sleep(2000);
+                event.close();
+            } catch (Exception e) {
+                // Test should fail. Report what went wrong.
+                e.printStackTrace();
+            }
+        }
+    }
+}
diff --git a/test/org/apache/catalina/core/TestApplicationFilterChain.java b/test/org/apache/catalina/core/TestApplicationFilterChain.java
index 8ffe12f..10f00c0 100644
--- a/test/org/apache/catalina/core/TestApplicationFilterChain.java
+++ b/test/org/apache/catalina/core/TestApplicationFilterChain.java
@@ -84,10 +84,10 @@
         }
 
         @Override
-        public void doFilter(ServletRequest request, ServletResponse response,
-                FilterChain chain) throws IOException, ServletException {
+        public void doFilter(final ServletRequest request, final ServletResponse response,
+                final FilterChain chain) throws IOException, ServletException {
 
-            AsyncContext ac = request.startAsync();
+            final AsyncContext ac = request.startAsync();
             ac.start(new Runnable() {
 
                 @Override
diff --git a/test/org/apache/catalina/core/TestAsyncContextImpl.java b/test/org/apache/catalina/core/TestAsyncContextImpl.java
index f0e63b3..975bcf2 100644
--- a/test/org/apache/catalina/core/TestAsyncContextImpl.java
+++ b/test/org/apache/catalina/core/TestAsyncContextImpl.java
@@ -541,8 +541,8 @@
             long timeoutDelay = TimeoutServlet.ASYNC_TIMEOUT;
             if (asyncDispatch != null && asyncDispatch.booleanValue() &&
                     !completeOnTimeout.booleanValue()) {
-                // The async dispatch includes a sleep
-                timeoutDelay += AsyncStartRunnable.THREAD_SLEEP_TIME;
+                // Extra timeout in this case
+                timeoutDelay += TimeoutServlet.ASYNC_TIMEOUT;
             }
             alvGlobal.validateAccessLog(1, 200, timeoutDelay,
                     timeoutDelay + TIMEOUT_MARGIN + REQUEST_TIME);
@@ -557,7 +557,7 @@
         private final Boolean completeOnTimeout;
         private final String dispatchUrl;
 
-        public static final long ASYNC_TIMEOUT = 100;
+        public static final long ASYNC_TIMEOUT = 3000;
 
         public TimeoutServlet(Boolean completeOnTimeout, String dispatchUrl) {
             this.completeOnTimeout = completeOnTimeout;
@@ -1371,7 +1371,7 @@
         protected void doGet(HttpServletRequest req, final HttpServletResponse resp)
                 throws ServletException, IOException {
 
-            AsyncContext actxt = req.startAsync();
+            final AsyncContext actxt = req.startAsync();
             actxt.setTimeout(TIMEOUT);
             if (threaded) {
                 actxt.start(new Runnable() {
diff --git a/test/org/apache/catalina/core/TestSwallowAbortedUploads.java b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
index 9fb83a8..94c37be 100644
--- a/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
+++ b/test/org/apache/catalina/core/TestSwallowAbortedUploads.java
@@ -131,7 +131,7 @@
         AbortedUploadClient client = new AbortedUploadClient();
         Exception ex = doAbortedUploadTest(client, true, false);
         assertTrue("Limited upload with swallow disabled does not generate client exception",
-                   ex instanceof java.net.SocketException);
+                   ex != null && ex instanceof java.net.SocketException);
         client.reset();
     }
 
@@ -177,7 +177,7 @@
         AbortedPOSTClient client = new AbortedPOSTClient();
         Exception ex = doAbortedPOSTTest(client, HttpServletResponse.SC_REQUEST_ENTITY_TOO_LARGE, false);
         assertTrue("Limited upload with swallow disabled does not generate client exception",
-                   ex instanceof java.net.SocketException);
+                   ex != null && ex instanceof java.net.SocketException);
         client.reset();
     }
 
diff --git a/test/org/apache/catalina/mbeans/TestRegistration.java b/test/org/apache/catalina/mbeans/TestRegistration.java
index 0fb404d..d4c76b6 100644
--- a/test/org/apache/catalina/mbeans/TestRegistration.java
+++ b/test/org/apache/catalina/mbeans/TestRegistration.java
@@ -170,6 +170,11 @@
         combinedRealm.addRealm(nullRealm);
         ctx.setRealm(combinedRealm);
 
+        // Disable keep-alive otherwise request processing threads in keep-alive
+        // won't shut down fast enough with BIO to de-register the processor
+        // triggering a test failure
+        tomcat.getConnector().setAttribute("maxKeepAliveRequests", Integer.valueOf(1));
+
         tomcat.start();
 
         getUrl("http://localhost:" + getPort());
@@ -190,10 +195,12 @@
         String protocol = tomcat.getConnector().getProtocolHandlerClassName();
         if (protocol.indexOf("Nio2") > 0) {
             protocol = "nio2";
+        } else if (protocol.indexOf("Nio") > 0) {
+            protocol = "nio";
         } else if (protocol.indexOf("Apr") > 0) {
             protocol = "apr";
         } else {
-            protocol = "nio";
+            protocol = "bio";
         }
         String index = tomcat.getConnector().getProperty("nameIndex").toString();
         ArrayList<String> expected = new ArrayList<>(Arrays.asList(basicMBeanNames()));
diff --git a/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java b/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java
index 532049c..3f47769 100644
--- a/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java
+++ b/test/org/apache/catalina/nonblocking/TestNonBlockingAPI.java
@@ -41,6 +41,7 @@
 import javax.servlet.http.HttpServletResponse;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
@@ -90,6 +91,12 @@
 
     @Test(expected=IOException.class)
     public void testNonBlockingReadIgnoreIsReady() throws Exception {
+        // TODO Investigate options to get this test to pass with the HTTP BIO
+        //      connector.
+        Assume.assumeFalse(
+                "Skipping as this test requires true non-blocking IO",
+                getTomcatInstance().getConnector().getProtocol()
+                        .equals("org.apache.coyote.http11.Http11Protocol"));
         doTestNonBlockingRead(true);
     }
 
diff --git a/test/org/apache/catalina/startup/TomcatBaseTest.java b/test/org/apache/catalina/startup/TomcatBaseTest.java
index 4f768b4..59c1d15 100644
--- a/test/org/apache/catalina/startup/TomcatBaseTest.java
+++ b/test/org/apache/catalina/startup/TomcatBaseTest.java
@@ -66,7 +66,6 @@
  * don't have to keep writing the cleanup code.
  */
 public abstract class TomcatBaseTest extends LoggingBaseTest {
-    private static final int DEFAULT_CLIENT_TIMEOUT_MS = 300_000;
     private Tomcat tomcat;
     private boolean accessLogEnabled = false;
 
@@ -606,13 +605,13 @@
 
     public static int headUrl(String path, ByteChunk out,
             Map<String, List<String>> resHead) throws IOException {
-        return methodUrl(path, out, DEFAULT_CLIENT_TIMEOUT_MS, null, resHead, "HEAD");
+        return methodUrl(path, out, 1000000, null, resHead, "HEAD");
     }
 
     public static int getUrl(String path, ByteChunk out,
             Map<String, List<String>> reqHead,
             Map<String, List<String>> resHead) throws IOException {
-        return getUrl(path, out, DEFAULT_CLIENT_TIMEOUT_MS, reqHead, resHead);
+        return getUrl(path, out, 1000000, reqHead, resHead);
     }
 
     public static int getUrl(String path, ByteChunk out, int readTimeout,
@@ -747,6 +746,10 @@
                 os.write(next);
                 os.flush();
             }
+        } catch (IOException ioe) {
+            // Failed to write the request body. Server may have closed the
+            // connection.
+            ioe.printStackTrace();
         }
 
         int rc = connection.getResponseCode();
diff --git a/test/org/apache/catalina/tribes/demos/IntrospectionUtils.java b/test/org/apache/catalina/tribes/demos/IntrospectionUtils.java
index 258d481..62f5672 100644
--- a/test/org/apache/catalina/tribes/demos/IntrospectionUtils.java
+++ b/test/org/apache/catalina/tribes/demos/IntrospectionUtils.java
@@ -23,16 +23,14 @@
 import java.net.UnknownHostException;
 import java.util.Hashtable;
 
-import org.apache.juli.logging.Log;
-import org.apache.juli.logging.LogFactory;
-
 /**
  * Utils for introspection and reflection
  */
 public final class IntrospectionUtils {
 
 
-    private static final Log log = LogFactory.getLog(IntrospectionUtils.class);
+    private static final org.apache.juli.logging.Log log=
+        org.apache.juli.logging.LogFactory.getLog( IntrospectionUtils.class );
 
     /*
      * Find a method with the right name If found, call the method ( if param is
diff --git a/test/org/apache/catalina/tribes/test/NioSenderTest.java b/test/org/apache/catalina/tribes/test/NioSenderTest.java
index 43c7112..01edaa0 100644
--- a/test/org/apache/catalina/tribes/test/NioSenderTest.java
+++ b/test/org/apache/catalina/tribes/test/NioSenderTest.java
@@ -50,7 +50,12 @@
     }
 
     public void init() throws Exception {
-        selector = Selector.open();
+        synchronized (Selector.class) {
+            // Selector.open() isn't thread safe
+            // http://bugs.sun.com/view_bug.do?bug_id=6427854
+            // Affects 1.6.0_29, fixed in 1.7.0_01
+            selector = Selector.open();
+        }
         mbr = new MemberImpl("localhost",4444,0);
         NioSender sender = new NioSender();
         sender.setDestination(mbr);
diff --git a/test/org/apache/catalina/tribes/test/transport/SocketNioSend.java b/test/org/apache/catalina/tribes/test/transport/SocketNioSend.java
index e865dd7..5c71c6d 100644
--- a/test/org/apache/catalina/tribes/test/transport/SocketNioSend.java
+++ b/test/org/apache/catalina/tribes/test/transport/SocketNioSend.java
@@ -32,7 +32,13 @@
 public class SocketNioSend {
 
     public static void main(String[] args) throws Exception {
-        Selector selector = Selector.open();
+        Selector selector;
+        synchronized (Selector.class) {
+            // Selector.open() isn't thread safe
+            // http://bugs.sun.com/view_bug.do?bug_id=6427854
+            // Affects 1.6.0_29, fixed in 1.7.0_01
+            selector = Selector.open();
+        }
         Member mbr = new MemberImpl("localhost", 9999, 0);
         ChannelData data = new ChannelData();
         data.setOptions(Channel.SEND_OPTIONS_BYTE_MESSAGE);
diff --git a/test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java b/test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java
index 0a95826..f15f2f9 100644
--- a/test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java
+++ b/test/org/apache/catalina/tribes/test/transport/SocketNioValidateSend.java
@@ -30,7 +30,13 @@
 public class SocketNioValidateSend {
 
     public static void main(String[] args) throws Exception {
-        Selector selector = Selector.open();
+        Selector selector;
+        synchronized (Selector.class) {
+            // Selector.open() isn't thread safe
+            // http://bugs.sun.com/view_bug.do?bug_id=6427854
+            // Affects 1.6.0_29, fixed in 1.7.0_01
+            selector = Selector.open();
+        }
         Member mbr = new MemberImpl("localhost", 9999, 0);
         byte seq = 0;
         byte[] buf = new byte[50000];
diff --git a/test/org/apache/catalina/util/TestConversions.java b/test/org/apache/catalina/util/TestConversions.java
new file mode 100644
index 0000000..110a0e5
--- /dev/null
+++ b/test/org/apache/catalina/util/TestConversions.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.catalina.util;
+
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+import org.junit.Test;
+
+public class TestConversions {
+
+    @Test
+    public void testByteArrayToLong() throws IOException {
+        assertEquals(0L, Conversions.byteArrayToLong(new byte[] { 0 }));
+        assertEquals(1L, Conversions.byteArrayToLong(new byte[] { 1 }));
+        assertEquals(0xFF, Conversions.byteArrayToLong(new byte[] { -1 }));
+        assertEquals(0xFFFF,
+                Conversions.byteArrayToLong(new byte[] { -1, -1 }));
+        assertEquals(0xFFFFFF,
+                Conversions.byteArrayToLong(new byte[] { -1, -1, -1 }));
+    }
+
+}
diff --git a/test/org/apache/coyote/ajp/SimpleAjpClient.java b/test/org/apache/coyote/ajp/SimpleAjpClient.java
index e5d32bc..29414db 100644
--- a/test/org/apache/coyote/ajp/SimpleAjpClient.java
+++ b/test/org/apache/coyote/ajp/SimpleAjpClient.java
@@ -378,8 +378,9 @@
         TesterAjpMessage message = new TesterAjpMessage(AJP_PACKET_SIZE);
 
         byte[] buf = message.getBuffer();
+        int headerLength = message.getHeaderLength();
 
-        read(is, buf, 0, Constants.H_SIZE);
+        read(is, buf, 0, headerLength);
 
         int messageLength = message.processHeader(false);
         if (messageLength < 0) {
@@ -393,7 +394,7 @@
                         "] for buffer length [" +
                         Integer.valueOf(buf.length) + "]");
             }
-            read(is, buf, Constants.H_SIZE, messageLength);
+            read(is, buf, headerLength, messageLength);
             return message;
         }
     }
diff --git a/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java b/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
index 3e505a2..ab39f1e 100644
--- a/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
+++ b/test/org/apache/coyote/ajp/TestAbstractAjpProcessor.java
@@ -49,15 +49,17 @@
         // Has a protocol been specified
         String protocol = System.getProperty("tomcat.test.protocol");
 
-        // Use NIO by default
+        // Use BIO by default
         if (protocol == null) {
-            protocol = "org.apache.coyote.ajp.AjpNioProtocol";
+            protocol = "org.apache.coyote.ajp.AjpProtocol";
         } else if (protocol.contains("Nio2")) {
             protocol = "org.apache.coyote.ajp.AjpNio2Protocol";
+        } else if (protocol.contains("Nio")) {
+            protocol = "org.apache.coyote.ajp.AjpNioProtocol";
         } else if (protocol.contains("Apr")) {
             protocol = "org.apache.coyote.ajp.AjpAprProtocol";
         } else {
-            protocol = "org.apache.coyote.ajp.AjpNioProtocol";
+            protocol = "org.apache.coyote.ajp.AjpProtocol";
         }
 
         return protocol;
@@ -844,8 +846,8 @@
 
         public ReadBodyServlet(boolean callAvailable) {
             this.callAvailable = callAvailable;
-            this.availableList = callAvailable ? new ArrayList<>() : null;
-            this.readList = callAvailable ? new ArrayList<>() : null;
+            this.availableList = callAvailable ? new ArrayList<Integer>() : null;
+            this.readList = callAvailable ? new ArrayList<Integer>() : null;
         }
 
         @Override
diff --git a/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java b/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java
index 4fb9c9a..5609f2d 100644
--- a/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java
+++ b/test/org/apache/coyote/http11/TestAbstractHttp11Processor.java
@@ -145,7 +145,7 @@
         String request =
             "POST /echo-params.jsp HTTP/1.1" + SimpleHttpClient.CRLF +
             "Host: any" + SimpleHttpClient.CRLF +
-            "Expect: unknown" + SimpleHttpClient.CRLF +
+            "Expect: unknoen" + SimpleHttpClient.CRLF +
             SimpleHttpClient.CRLF;
 
         Client client = new Client(getPort());
diff --git a/test/org/apache/coyote/http11/filters/TestGzipOutputFilter.java b/test/org/apache/coyote/http11/TestGzipOutputFilter.java
similarity index 80%
rename from test/org/apache/coyote/http11/filters/TestGzipOutputFilter.java
rename to test/org/apache/coyote/http11/TestGzipOutputFilter.java
index 3a766ef..a57c297 100644
--- a/test/org/apache/coyote/http11/filters/TestGzipOutputFilter.java
+++ b/test/org/apache/coyote/http11/TestGzipOutputFilter.java
@@ -15,7 +15,7 @@
  *  limitations under the License.
  */
 
-package org.apache.coyote.http11.filters;
+package org.apache.coyote.http11;
 
 import java.io.ByteArrayOutputStream;
 import java.util.zip.GZIPOutputStream;
@@ -25,6 +25,7 @@
 import org.junit.Test;
 
 import org.apache.coyote.Response;
+import org.apache.coyote.http11.filters.GzipOutputFilter;
 import org.apache.tomcat.util.buf.ByteChunk;
 
 /**
@@ -34,7 +35,7 @@
 public class TestGzipOutputFilter {
 
     /*
-     * Test the interaction between gzip and flushing. The idea is to: 1. create
+     * Test the interaction betwen gzip and flushing. The idea is to: 1. create
      * a internal output buffer, response, and attach an active gzipoutputfilter
      * to the output buffer 2. set the output stream of the internal buffer to
      * be a ByteArrayOutputStream so we can inspect the output bytes 3. write a
@@ -51,28 +52,30 @@
     public void testFlushingWithGzip() throws Exception {
         // set up response, InternalOutputBuffer, and ByteArrayOutputStream
         Response res = new Response();
-        TesterOutputBuffer tob = new TesterOutputBuffer(res, 8 * 1024);
-        res.setOutputBuffer(tob);
+        InternalOutputBuffer iob = new InternalOutputBuffer(res, 8 * 1024);
+        ByteArrayOutputStream bos = new ByteArrayOutputStream();
+        iob.outputStream = bos;
+        res.setOutputBuffer(iob);
 
-        // set up GzipOutputFilter to attach to the TesterOutputBuffer
+        // set up GzipOutputFilter to attach to the InternalOutputBuffer
         GzipOutputFilter gf = new GzipOutputFilter();
-        tob.addFilter(gf);
-        tob.addActiveFilter(gf);
+        iob.addFilter(gf);
+        iob.addActiveFilter(gf);
 
         // write a chunk out
         ByteChunk chunk = new ByteChunk(1024);
         byte[] d = "Hello there tomcat developers, there is a bug in JDK".getBytes();
         chunk.append(d, 0, d.length);
-        tob.doWrite(chunk, res);
+        iob.doWrite(chunk, res);
 
         // flush the InternalOutputBuffer
-        tob.flush();
+        iob.flush();
 
         // read from the ByteArrayOutputStream to find out what's being written
         // out (flushed)
-        byte[] dataFound = tob.toByteArray();
+        byte[] dataFound = bos.toByteArray();
 
-        // find out what's expected by writing to GZIPOutputStream and close it
+        // find out what's expected by wrting to GZIPOutputStream and close it
         // (to force flushing)
         ByteArrayOutputStream gbos = new ByteArrayOutputStream(1024);
         GZIPOutputStream gos = new GZIPOutputStream(gbos);
diff --git a/test/org/apache/coyote/http11/TestInternalOutputBuffer.java b/test/org/apache/coyote/http11/TestInternalOutputBuffer.java
deleted file mode 100644
index be88176..0000000
--- a/test/org/apache/coyote/http11/TestInternalOutputBuffer.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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.coyote.http11;
-
-import org.junit.Assert;
-import org.junit.Ignore;
-import org.junit.Test;
-
-import org.apache.catalina.Context;
-import org.apache.catalina.startup.SimpleHttpClient;
-import org.apache.catalina.startup.Tomcat;
-import org.apache.catalina.startup.TomcatBaseTest;
-
-public class TestInternalOutputBuffer extends TomcatBaseTest {
-
-    @Test
-    @Ignore // Currently fails for NIO2 since that implementation writes the ACK
-            // to the buffers rather than directly to the socket.
-    public void testSendAck() throws Exception {
-        Tomcat tomcat = getTomcatInstance();
-
-        // No file system docBase required
-        Context ctx = tomcat.addContext("", null);
-
-        Tomcat.addServlet(ctx, "echo", new EchoBodyServlet());
-        ctx.addServletMapping("/echo", "echo");
-
-        tomcat.start();
-
-        ExpectationClient client = new ExpectationClient();
-
-        client.setPort(tomcat.getConnector().getLocalPort());
-
-        client.connect();
-
-        client.doRequestHeaders();
-        Assert.assertTrue(client.isResponse100());
-
-        client.doRequestBody();
-        Assert.assertTrue(client.isResponse200());
-        Assert.assertTrue(client.isResponseBodyOK());
-    }
-
-    private static class ExpectationClient extends SimpleHttpClient {
-
-        private static final String BODY = "foo=bar";
-
-        public void doRequestHeaders() throws Exception {
-            StringBuilder requestHeaders = new StringBuilder();
-            requestHeaders.append("POST /echo HTTP/1.1").append(CRLF);
-            requestHeaders.append("Host: localhost").append(CRLF);
-            requestHeaders.append("Expect: 100-continue").append(CRLF);
-            requestHeaders.append("Content-Type: application/x-www-form-urlencoded").append(CRLF);
-            String len = Integer.toString(BODY.length());
-            requestHeaders.append("Content-length: ").append(len).append(CRLF);
-            requestHeaders.append(CRLF);
-
-            setRequest(new String[] {requestHeaders.toString()});
-
-            processRequest(false);
-        }
-
-        public void doRequestBody() throws Exception {
-            setRequest(new String[] { BODY });
-
-            processRequest(true);
-        }
-
-        @Override
-        public boolean isResponseBodyOK() {
-            return BODY.equals(getResponseBody());
-        }
-    }
-}
diff --git a/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java b/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java
deleted file mode 100644
index bbebab2..0000000
--- a/test/org/apache/coyote/http11/filters/TesterOutputBuffer.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- *  Licensed to the Apache Software Foundation (ASF) under one or more
- *  contributor license agreements.  See the NOTICE file distributed with
- *  this work for additional information regarding copyright ownership.
- *  The ASF licenses this file to You 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.coyote.http11.filters;
-
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.net.Socket;
-
-import org.apache.coyote.OutputBuffer;
-import org.apache.coyote.Response;
-import org.apache.coyote.http11.AbstractOutputBuffer;
-import org.apache.tomcat.util.buf.ByteChunk;
-import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.SocketWrapperBase;
-
-/**
- * Output buffer for use in unit tests. This is a minimal implementation.
- */
-public class TesterOutputBuffer extends AbstractOutputBuffer<Socket> {
-
-    /**
-     * Underlying output stream.
-     */
-    private ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
-
-
-    public TesterOutputBuffer(Response response, int headerBufferSize) {
-        super(response, headerBufferSize);
-        outputStreamOutputBuffer = new OutputStreamOutputBuffer();
-    }
-
-
-    // --------------------------------------------------------- Public Methods
-
-    @Override
-    public void init(SocketWrapperBase<Socket> socketWrapper,
-            AbstractEndpoint<Socket> endpoint) throws IOException {
-        // NO-OP: Unused
-    }
-
-
-    /**
-     * Recycle the output buffer. This should be called when closing the
-     * connection.
-     */
-    @Override
-    public void recycle() {
-        super.recycle();
-        outputStream = null;
-    }
-
-
-    // ------------------------------------------------ HTTP/1.1 Output Methods
-
-    /**
-     * Send an acknowledgement.
-     */
-    @Override
-    public void sendAck() {
-        // NO-OP: Unused
-    }
-
-
-    @Override
-    protected void commit() {
-        // NO-OP: Unused
-    }
-
-
-    @Override
-    protected boolean hasMoreDataToFlush() {
-        // Unused
-        return false;
-    }
-
-
-    @Override
-    protected void registerWriteInterest() {
-        // NO-OP: Unused
-    }
-
-
-    @Override
-    protected boolean flushBuffer(boolean block) throws IOException {
-        // Blocking IO so ignore block parameter as this will always use
-        // blocking IO.
-        // Always blocks so never any data left over.
-        return false;
-    }
-
-
-    @Override
-    protected void addToBB(byte[] buf, int offset, int length)
-            throws IOException {
-        // NO-OP: Unused
-    }
-
-
-    /*
-     * Expose data written for use by unit tests.
-     */
-    byte[] toByteArray() {
-        return outputStream.toByteArray();
-    }
-
-
-    /**
-     * This class is an output buffer which will write data to an output
-     * stream.
-     */
-    protected class OutputStreamOutputBuffer implements OutputBuffer {
-
-        /**
-         * Write chunk.
-         */
-        @Override
-        public int doWrite(ByteChunk chunk, Response res) throws IOException {
-            int length = chunk.getLength();
-            outputStream.write(chunk.getBuffer(), chunk.getStart(), length);
-            byteCount += chunk.getLength();
-            return chunk.getLength();
-        }
-
-        @Override
-        public long getBytesWritten() {
-            return byteCount;
-        }
-    }
-}
diff --git a/test/org/apache/tomcat/util/buf/TestB2CConverter.java b/test/org/apache/tomcat/util/buf/TestB2CConverter.java
index 068136b..5b069bf 100644
--- a/test/org/apache/tomcat/util/buf/TestB2CConverter.java
+++ b/test/org/apache/tomcat/util/buf/TestB2CConverter.java
@@ -92,7 +92,8 @@
                 maxLeftover <= B2CConverter.LEFTOVER_SIZE);
     }
 
-    @Test(expected=MalformedInputException.class)
+    // TODO Work-around bug in UTF8 decoder
+    //@Test(expected=MalformedInputException.class)
     public void testBug54602a() throws Exception {
         // Check invalid input is rejected straight away
         B2CConverter conv = new B2CConverter("UTF-8");
diff --git a/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java b/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java
index 4954c75..0ac6a0e 100644
--- a/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java
+++ b/test/org/apache/tomcat/util/net/jsse/TesterBug50640SslImpl.java
@@ -17,21 +17,24 @@
 package org.apache.tomcat.util.net.jsse;
 
 import org.apache.tomcat.util.net.AbstractEndpoint;
-import org.apache.tomcat.util.net.SSLUtil;
+import org.apache.tomcat.util.net.ServerSocketFactory;
 
 public class TesterBug50640SslImpl extends JSSEImplementation {
 
     public static final String PROPERTY_NAME = "bug50640";
     public static final String PROPERTY_VALUE = "pass";
 
-
     @Override
-    public SSLUtil getSSLUtil(AbstractEndpoint<?> endpoint) {
+    public ServerSocketFactory getServerSocketFactory(
+            AbstractEndpoint<?> endpoint)  {
+
+        // Check the custom attribute is visible & correcly set
         String flag = endpoint.getProperty(PROPERTY_NAME);
         if (PROPERTY_VALUE.equals(flag)) {
-            return super.getSSLUtil(endpoint);
+            return super.getServerSocketFactory(endpoint);
         } else {
             return null;
         }
     }
+
 }
diff --git a/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java b/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java
deleted file mode 100644
index 3c29a71..0000000
--- a/test/org/apache/tomcat/websocket/TestCaseInsensitiveKeyMap.java
+++ /dev/null
@@ -1,215 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.websocket;
-
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-
-import org.junit.Assert;
-import org.junit.Test;
-
-public class TestCaseInsensitiveKeyMap {
-
-    @Test
-    public void testPut() {
-        Object o1 = new Object();
-        Object o2 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-        Object o = map.put("A", o2);
-
-        Assert.assertEquals(o1,  o);
-
-        Assert.assertEquals(o2, map.get("a"));
-        Assert.assertEquals(o2, map.get("A"));
-    }
-
-
-    @Test(expected=NullPointerException.class)
-    public void testPutNullKey() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put(null, o1);
-    }
-
-
-    @Test
-    public void testGet() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Assert.assertEquals(o1, map.get("a"));
-        Assert.assertEquals(o1, map.get("A"));
-    }
-
-
-    @Test
-    public void testGetNullKey() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Assert.assertNull(map.get(null));
-    }
-
-
-    @Test
-    public void testContainsKey() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Assert.assertTrue(map.containsKey("a"));
-        Assert.assertTrue(map.containsKey("A"));
-    }
-
-
-    @Test
-    public void testContainsKeyNonString() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Assert.assertFalse(map.containsKey(o1));
-    }
-
-
-    @Test
-    public void testContainsKeyNull() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Assert.assertFalse(map.containsKey(null));
-    }
-
-
-    @Test
-    public void testContainsValue() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Assert.assertTrue(map.containsValue(o1));
-    }
-
-
-    @Test
-    public void testRemove() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-        Assert.assertFalse(map.isEmpty());
-        map.remove("A");
-        Assert.assertTrue(map.isEmpty());
-
-        map.put("A", o1);
-        Assert.assertFalse(map.isEmpty());
-        map.remove("a");
-        Assert.assertTrue(map.isEmpty());
-    }
-
-
-    @Test
-    public void testClear() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        for (int i = 0; i < 10; i++) {
-            map.put(Integer.toString(i), o1);
-        }
-        Assert.assertEquals(10, map.size());
-        map.clear();
-        Assert.assertEquals(0, map.size());
-    }
-
-
-    @Test
-    public void testPutAll() {
-        Object o1 = new Object();
-        Object o2 = new Object();
-
-        Map<String,Object> source = new HashMap<>();
-        source.put("a", o1);
-        source.put("A", o2);
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.putAll(source);
-
-        Assert.assertEquals(1, map.size());
-        Assert.assertTrue(map.containsValue(o1) != map.containsValue(o2));
-    }
-
-
-    @Test
-    public void testKeySetContains() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Set<String> keys = map.keySet();
-
-        Assert.assertTrue(keys.contains("a"));
-        Assert.assertTrue(keys.contains("A"));
-    }
-
-
-    @Test
-    public void testKeySetRemove() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Iterator<String> iter = map.keySet().iterator();
-        Assert.assertTrue(iter.hasNext());
-        iter.next();
-        iter.remove();
-        Assert.assertTrue(map.isEmpty());
-    }
-
-
-    @Test
-    public void testEntrySetRemove() {
-        Object o1 = new Object();
-
-        CaseInsensitiveKeyMap<Object> map = new CaseInsensitiveKeyMap<>();
-        map.put("a", o1);
-
-        Iterator<Entry<String,Object>> iter = map.entrySet().iterator();
-        Assert.assertTrue(iter.hasNext());
-        Entry<String,Object> entry = iter.next();
-        Assert.assertEquals("a", entry.getKey());
-        Assert.assertEquals(o1, entry.getValue());
-        iter.remove();
-        Assert.assertTrue(map.isEmpty());
-    }
-}
diff --git a/test/org/apache/tomcat/websocket/TestWebSocketFrameClientSSL.java b/test/org/apache/tomcat/websocket/TestWebSocketFrameClientSSL.java
index 141b18e..e2b0890 100644
--- a/test/org/apache/tomcat/websocket/TestWebSocketFrameClientSSL.java
+++ b/test/org/apache/tomcat/websocket/TestWebSocketFrameClientSSL.java
@@ -28,6 +28,7 @@
 import javax.websocket.WebSocketContainer;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
@@ -91,6 +92,13 @@
 
     @Test
     public void testBug56032() throws Exception {
+        // TODO Investigate options to get this test to pass with the HTTP BIO
+        //      connector.
+        Assume.assumeFalse(
+                "Skip this test on BIO. TODO: investigate options to make it pass with HTTP BIO connector",
+                getTomcatInstance().getConnector().getProtocol()
+                        .equals("org.apache.coyote.http11.Http11Protocol"));
+
         Tomcat tomcat = getTomcatInstance();
         // No file system docBase required
         Context ctx = tomcat.addContext("", null);
diff --git a/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java b/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java
index b692f5d..06e8f37 100644
--- a/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java
+++ b/test/org/apache/tomcat/websocket/TestWsRemoteEndpoint.java
@@ -62,35 +62,25 @@
 
     @Test
     public void testWriterAnnotation() throws Exception {
-        doTestWriter(TesterAnnotatedEndpoint.class, true, TEST_MESSAGE_5K);
+        doTestWriter(TesterAnnotatedEndpoint.class, true);
     }
 
     @Test
     public void testWriterProgrammatic() throws Exception {
-        doTestWriter(TesterProgrammaticEndpoint.class, true, TEST_MESSAGE_5K);
-    }
-
-    @Test
-    public void testWriterZeroLengthAnnotation() throws Exception {
-        doTestWriter(TesterAnnotatedEndpoint.class, true, "");
-    }
-
-    @Test
-    public void testWriterZeroLengthProgrammatic() throws Exception {
-        doTestWriter(TesterProgrammaticEndpoint.class, true, "");
+        doTestWriter(TesterProgrammaticEndpoint.class, true);
     }
 
     @Test
     public void testStreamAnnotation() throws Exception {
-        doTestWriter(TesterAnnotatedEndpoint.class, false, TEST_MESSAGE_5K);
+        doTestWriter(TesterAnnotatedEndpoint.class, false);
     }
 
     @Test
     public void testStreamProgrammatic() throws Exception {
-        doTestWriter(TesterProgrammaticEndpoint.class, false, TEST_MESSAGE_5K);
+        doTestWriter(TesterProgrammaticEndpoint.class, false);
     }
 
-    private void doTestWriter(Class<?> clazz, boolean useWriter, String testMessage) throws Exception {
+    private void doTestWriter(Class<?> clazz, boolean useWriter) throws Exception {
         Tomcat tomcat = getTomcatInstance();
         // No file system docBase required
         Context ctx = tomcat.addContext("", null);
@@ -133,7 +123,7 @@
             Writer w = wsSession.getBasicRemote().getSendWriter();
 
             for (int i = 0; i < 8; i++) {
-                w.write(testMessage);
+                w.write(TEST_MESSAGE_5K);
             }
 
             w.close();
@@ -141,7 +131,7 @@
             OutputStream s = wsSession.getBasicRemote().getSendStream();
 
             for (int i = 0; i < 8; i++) {
-                s.write(testMessage.getBytes(StandardCharsets.UTF_8));
+                s.write(TEST_MESSAGE_5K.getBytes(StandardCharsets.UTF_8));
             }
 
             s.close();
@@ -172,77 +162,20 @@
         int offset = 0;
         int i = 0;
         for (String result : results) {
-            if (testMessage.length() == 0) {
-                Assert.assertEquals(0, result.length());
-            } else {
-                // First may be a fragment
-                Assert.assertEquals(SEQUENCE.substring(offset, S_LEN),
-                        result.substring(0, S_LEN - offset));
-                i = S_LEN - offset;
-                while (i + S_LEN < result.length()) {
-                    if (!SEQUENCE.equals(result.substring(i, i + S_LEN))) {
-                        Assert.fail();
-                    }
-                    i += S_LEN;
-                }
-                offset = result.length() - i;
-                if (!SEQUENCE.substring(0, offset).equals(result.substring(i))) {
+            // First may be a fragment
+            Assert.assertEquals(SEQUENCE.substring(offset, S_LEN),
+                    result.substring(0, S_LEN - offset));
+            i = S_LEN - offset;
+            while (i + S_LEN < result.length()) {
+                if (!SEQUENCE.equals(result.substring(i, i + S_LEN))) {
                     Assert.fail();
                 }
+                i += S_LEN;
+            }
+            offset = result.length() - i;
+            if (!SEQUENCE.substring(0, offset).equals(result.substring(i))) {
+                Assert.fail();
             }
         }
     }
-
-    @Test
-    public void testWriterErrorAnnotation() throws Exception {
-        doTestWriterError(TesterAnnotatedEndpoint.class);
-    }
-
-    @Test
-    public void testWriterErrorProgrammatic() throws Exception {
-        doTestWriterError(TesterProgrammaticEndpoint.class);
-    }
-
-    private void doTestWriterError(Class<?> clazz) throws Exception {
-        Tomcat tomcat = getTomcatInstance();
-        // No file system docBase required
-        Context ctx = tomcat.addContext("", null);
-        ctx.addApplicationListener(TesterEchoServer.Config.class.getName());
-        Tomcat.addServlet(ctx, "default", new DefaultServlet());
-        ctx.addServletMapping("/", "default");
-
-        WebSocketContainer wsContainer = ContainerProvider.getWebSocketContainer();
-
-        tomcat.start();
-
-        Session wsSession;
-        URI uri = new URI("ws://localhost:" + getPort() + TesterEchoServer.Config.PATH_WRITER_ERROR);
-        if (Endpoint.class.isAssignableFrom(clazz)) {
-            @SuppressWarnings("unchecked")
-            Class<? extends Endpoint> endpointClazz = (Class<? extends Endpoint>) clazz;
-            wsSession = wsContainer.connectToServer(endpointClazz, Builder.create().build(), uri);
-        } else {
-            wsSession = wsContainer.connectToServer(clazz, uri);
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-        TesterEndpoint tep = (TesterEndpoint) wsSession.getUserProperties().get("endpoint");
-        tep.setLatch(latch);
-        AsyncHandler<?> handler;
-        handler = new AsyncText(latch);
-
-        wsSession.addMessageHandler(handler);
-
-        // This should trigger the error
-        wsSession.getBasicRemote().sendText("Start");
-
-        boolean latchResult = handler.getLatch().await(10, TimeUnit.SECONDS);
-
-        Assert.assertTrue(latchResult);
-
-        @SuppressWarnings("unchecked")
-        List<String> messages = (List<String>) handler.getMessages();
-
-        Assert.assertEquals(0, messages.size());
-    }
 }
diff --git a/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java b/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
index 7397210..1f1faa7 100644
--- a/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
+++ b/test/org/apache/tomcat/websocket/TestWsWebSocketContainer.java
@@ -45,12 +45,14 @@
 import javax.websocket.server.ServerEndpointConfig;
 
 import org.junit.Assert;
+import org.junit.Assume;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
 import org.apache.catalina.servlets.DefaultServlet;
 import org.apache.catalina.startup.Tomcat;
 import org.apache.catalina.startup.TomcatBaseTest;
+import org.apache.coyote.http11.Http11Protocol;
 import org.apache.tomcat.util.net.TesterSupport;
 import org.apache.tomcat.websocket.TesterMessageCountClient.BasicBinary;
 import org.apache.tomcat.websocket.TesterMessageCountClient.BasicHandler;
@@ -387,6 +389,11 @@
     private void doTestWriteTimeoutServer(boolean setTimeoutOnContainer)
             throws Exception {
 
+        // This will never work for BIO
+        Assume.assumeFalse(
+                "Skipping test. This feature will never work for BIO connector.",
+                getProtocol().equals(Http11Protocol.class.getName()));
+
         /*
          * Note: There are all sorts of horrible uses of statics in this test
          *       because the API uses classes and the tests really need access
diff --git a/test/org/apache/tomcat/websocket/TesterEchoServer.java b/test/org/apache/tomcat/websocket/TesterEchoServer.java
index 8dc5b34..406d3a8 100644
--- a/test/org/apache/tomcat/websocket/TesterEchoServer.java
+++ b/test/org/apache/tomcat/websocket/TesterEchoServer.java
@@ -37,7 +37,6 @@
         public static final String PATH_BASIC = "/echoBasic";
         public static final String PATH_BASIC_LIMIT_LOW = "/echoBasicLimitLow";
         public static final String PATH_BASIC_LIMIT_HIGH = "/echoBasicLimitHigh";
-        public static final String PATH_WRITER_ERROR = "/echoWriterError";
 
         @Override
         public void contextInitialized(ServletContextEvent sce) {
@@ -50,14 +49,12 @@
                 sc.addEndpoint(Basic.class);
                 sc.addEndpoint(BasicLimitLow.class);
                 sc.addEndpoint(BasicLimitHigh.class);
-                sc.addEndpoint(WriterError.class);
             } catch (DeploymentException e) {
                 throw new IllegalStateException(e);
             }
         }
     }
 
-
     @ServerEndpoint("/echoAsync")
     public static class Async {
 
@@ -189,24 +186,4 @@
         }
     }
 
-
-    @ServerEndpoint("/echoWriterError")
-    public static class WriterError {
-
-        @OnMessage
-        public void echoTextMessage(Session session, @SuppressWarnings("unused") String msg) {
-            try {
-                session.getBasicRemote().getSendWriter();
-                // Simulate an error
-                throw new RuntimeException();
-            } catch (IOException e) {
-                // Should not happen
-                try {
-                    session.close();
-                } catch (IOException e1) {
-                    // Ignore
-                }
-            }
-        }
-    }
 }
diff --git a/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java b/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java
index 221e60b..ed198fd 100644
--- a/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java
+++ b/test/org/apache/tomcat/websocket/pojo/TestEncodingDecoding.java
@@ -39,7 +39,6 @@
 import javax.websocket.EndpointConfig;
 import javax.websocket.Extension;
 import javax.websocket.MessageHandler;
-import javax.websocket.OnError;
 import javax.websocket.OnMessage;
 import javax.websocket.Session;
 import javax.websocket.WebSocketContainer;
@@ -48,7 +47,6 @@
 import javax.websocket.server.ServerEndpointConfig;
 
 import org.junit.Assert;
-import org.junit.Ignore;
 import org.junit.Test;
 
 import org.apache.catalina.Context;
@@ -62,12 +60,9 @@
 public class TestEncodingDecoding extends TomcatBaseTest {
 
     private static final String MESSAGE_ONE = "message-one";
-    private static final String MESSAGE_TWO = "message-two";
     private static final String PATH_PROGRAMMATIC_EP = "/echoProgrammaticEP";
     private static final String PATH_ANNOTATED_EP = "/echoAnnotatedEP";
     private static final String PATH_GENERICS_EP = "/echoGenericsEP";
-    private static final String PATH_MESSAGES_EP = "/echoMessagesEP";
-    private static final String PATH_BATCHED_EP = "/echoBatchedEP";
 
 
     @Test
@@ -220,119 +215,6 @@
         session.close();
     }
 
-    @Test
-    @Ignore // TODO Investigate why this test fails
-    public void testMessagesEndPoints() throws Exception {
-        // Set up utility classes
-        MessagesServer server = new MessagesServer();
-        SingletonConfigurator.setInstance(server);
-        ServerConfigListener.setPojoClazz(MessagesServer.class);
-
-        Tomcat tomcat = getTomcatInstance();
-        // No file system docBase required
-        Context ctx = tomcat.addContext("", null);
-        ctx.addApplicationListener(ServerConfigListener.class.getName());
-        Tomcat.addServlet(ctx, "default", new DefaultServlet());
-        ctx.addServletMapping("/", "default");
-
-        WebSocketContainer wsContainer =
-                ContainerProvider.getWebSocketContainer();
-
-        tomcat.start();
-
-        StringClient client = new StringClient();
-        URI uri = new URI("ws://localhost:" + getPort() + PATH_MESSAGES_EP);
-        Session session = wsContainer.connectToServer(client, uri);
-
-        session.getBasicRemote().sendText(MESSAGE_ONE);
-
-        // Should not take very long
-        int i = 0;
-        while (i < 20) {
-            if (server.received.size() > 0 && client.received.size() > 0) {
-                break;
-            }
-            Thread.sleep(100);
-        }
-
-        // Check messages were received
-        Assert.assertEquals(1, server.received.size());
-        Assert.assertEquals(1, client.received.size());
-
-        // Check correct messages were received
-        Assert.assertEquals(MESSAGE_ONE, server.received.peek());
-        session.close();
-
-        Assert.assertNull(server.t);
-
-        // Should not take very long but some failures have been seen
-        i = testEvent(MsgStringEncoder.class.getName()+":init", 0);
-        i = testEvent(MsgStringDecoder.class.getName()+":init", i);
-        i = testEvent(MsgByteEncoder.class.getName()+":init", i);
-        i = testEvent(MsgByteDecoder.class.getName()+":init", i);
-        i = testEvent(MsgStringEncoder.class.getName()+":destroy", i);
-        i = testEvent(MsgStringDecoder.class.getName()+":destroy", i);
-        i = testEvent(MsgByteEncoder.class.getName()+":destroy", i);
-        i = testEvent(MsgByteDecoder.class.getName()+":destroy", i);
-    }
-
-
-    @Test
-    @Ignore // TODO Investigate why this test fails
-    public void testBatchedEndPoints() throws Exception {
-        // Set up utility classes
-        BatchedServer server = new BatchedServer();
-        SingletonConfigurator.setInstance(server);
-        ServerConfigListener.setPojoClazz(BatchedServer.class);
-
-        Tomcat tomcat = getTomcatInstance();
-        // No file system docBase required
-        Context ctx = tomcat.addContext("", null);
-        ctx.addApplicationListener(ServerConfigListener.class.getName());
-        Tomcat.addServlet(ctx, "default", new DefaultServlet());
-        ctx.addServletMapping("/", "default");
-
-        WebSocketContainer wsContainer =
-                ContainerProvider.getWebSocketContainer();
-
-        tomcat.start();
-
-        StringClient client = new StringClient();
-        URI uri = new URI("ws://localhost:" + getPort() + PATH_BATCHED_EP);
-        Session session = wsContainer.connectToServer(client, uri);
-
-        session.getBasicRemote().sendText(MESSAGE_ONE);
-
-        // Should not take very long
-        int i = 0;
-        while (i++ < 20) {
-            if (server.received.size() > 0 && client.received.size() > 0) {
-                break;
-            }
-            Thread.sleep(100);
-        }
-
-        // Check messages were received
-        Assert.assertEquals(1, server.received.size());
-        Assert.assertEquals(2, client.received.size());
-
-        // Check correct messages were received
-        Assert.assertEquals(MESSAGE_ONE, server.received.peek());
-        session.close();
-
-        Assert.assertNull(server.t);
-
-        // Should not take very long but some failures have been seen
-        i = testEvent(MsgStringEncoder.class.getName()+":init", 0);
-        i = testEvent(MsgStringDecoder.class.getName()+":init", i);
-        i = testEvent(MsgByteEncoder.class.getName()+":init", i);
-        i = testEvent(MsgByteDecoder.class.getName()+":init", i);
-        i = testEvent(MsgStringEncoder.class.getName()+":destroy", i);
-        i = testEvent(MsgStringDecoder.class.getName()+":destroy", i);
-        i = testEvent(MsgByteEncoder.class.getName()+":destroy", i);
-        i = testEvent(MsgByteDecoder.class.getName()+":destroy", i);
-    }
-
 
     private int testEvent(String name, int count) throws InterruptedException {
         int i = count;
@@ -378,19 +260,6 @@
     }
 
 
-    @ClientEndpoint
-    public static class StringClient {
-
-        private Queue<Object> received = new ConcurrentLinkedQueue<>();
-
-        @OnMessage
-        public void rx(String in) {
-            received.add(in);
-        }
-
-    }
-
-
     @ServerEndpoint(value=PATH_GENERICS_EP,
             decoders={ListStringDecoder.class},
             encoders={ListStringEncoder.class},
@@ -409,50 +278,6 @@
     }
 
 
-    @ServerEndpoint(value=PATH_MESSAGES_EP,
-            configurator=SingletonConfigurator.class)
-    public static class MessagesServer {
-
-        private final Queue<String> received = new ConcurrentLinkedQueue<>();
-        private volatile Throwable t = null;
-
-        @OnMessage
-        public String onMessage(String message, Session session) {
-            received.add(message);
-            session.getAsyncRemote().sendText(MESSAGE_ONE);
-            return message;
-        }
-
-        @OnError
-        public void onError(@SuppressWarnings("unused") Session session, Throwable t) {
-            t.printStackTrace();
-            this.t = t;
-        }
-    }
-
-
-    @ServerEndpoint(value=PATH_BATCHED_EP,
-            configurator=SingletonConfigurator.class)
-    public static class BatchedServer {
-
-        private final Queue<String> received = new ConcurrentLinkedQueue<>();
-        private volatile Throwable t = null;
-
-        @OnMessage
-        public String onMessage(String message, Session session) throws IOException {
-            received.add(message);
-            session.getAsyncRemote().setBatchingAllowed(true);
-            session.getAsyncRemote().sendText(MESSAGE_ONE);
-            return MESSAGE_TWO;
-        }
-
-        @OnError
-        public void onError(@SuppressWarnings("unused") Session session, Throwable t) {
-            t.printStackTrace();
-            this.t = t;
-        }
-    }
-
     @ServerEndpoint(value=PATH_ANNOTATED_EP,
             decoders={MsgStringDecoder.class, MsgByteDecoder.class},
             encoders={MsgStringEncoder.class, MsgByteEncoder.class},
diff --git a/test/webapp/index.html b/test/webapp/index.html
index 392fa41..2bf0cef 100644
--- a/test/webapp/index.html
+++ b/test/webapp/index.html
@@ -1,19 +1,3 @@
-<!--
-  Licensed to the Apache Software Foundation (ASF) under one or more
-  contributor license agreements.  See the NOTICE file distributed with
-  this work for additional information regarding copyright ownership.
-  The ASF licenses this file to You 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.
--->
 <html>
   <head>
     <title>Index page</title>
diff --git a/test/webapp/index.html.gz b/test/webapp/index.html.gz
index 5aca6e9..c80f8bb 100644
--- a/test/webapp/index.html.gz
+++ b/test/webapp/index.html.gz
Binary files differ
diff --git a/webapps/docs/aio.xml b/webapps/docs/aio.xml
index 3ea4836..26aa47c 100644
--- a/webapps/docs/aio.xml
+++ b/webapps/docs/aio.xml
@@ -36,17 +36,288 @@
   <section name="Introduction">
 
   <p>
-    <b>IMPORTANT NOTE: Usage of these features requires using the
-    HTTP connectors. The AJP connectors do not support them.</b>
+    With usage of APR or NIO APIs as the basis of its connectors, Tomcat is
+    able to provide a number of extensions over the regular blocking IO
+    as provided with support for the Servlet API.
   </p>
 
+  <p>
+    <b>IMPORTANT NOTE: Usage of these features requires using the APR or NIO
+    HTTP connectors. The classic java.io HTTP connector and the AJP connectors
+    do not support them.</b>
+  </p>
+
+  </section>
+
+  <section name="Comet support">
+
+  <p>
+    Comet support allows a servlet to process IO asynchronously, receiving
+    events when data is available for reading on the connection (rather than
+    always using a blocking read), and writing data back on connections
+    asynchronously (most likely responding to some event raised from some
+    other source).
+  </p>
+
+  <subsection name="CometEvent">
+
+  <p>
+    Servlets which implement the <code>org.apache.catalina.comet.CometProcessor</code>
+    interface will have their event method invoked rather than the usual service
+    method, according to the event which occurred. The event object gives
+    access to the usual request and response objects, which may be used in the
+    usual way. The main difference is that those objects remain valid and fully
+    functional at any time between processing of the BEGIN event until processing
+    an END or ERROR event.
+    The following event types exist:
+  </p>
+
+  <ul>
+  <li>EventType.BEGIN: will be called at the beginning
+     of the processing of the connection. It can be used to initialize any relevant
+     fields using the request and response objects. Between the end of the processing
+     of this event, and the beginning of the processing of the end or error events,
+     it is possible to use the response object to write data on the open connection.
+     Note that the response object and dependent OutputStream and Writer are still
+     not synchronized, so when they are accessed by multiple threads,
+     synchronization is mandatory. After processing the initial event, the request
+     is considered to be committed.</li>
+  <li>EventType.READ: This indicates that input data is available, and that one read can be made
+       without blocking. The available and ready methods of the InputStream or
+       Reader may be used to determine if there is a risk of blocking: the servlet
+       should read while data is reported available. When encountering a read error,
+       the servlet should report it by propagating the exception properly. Throwing
+       an exception will cause the error event to be invoked, and the connection
+       will be closed.
+       Alternately, it is also possible to catch any exception, perform clean up
+       on any data structure the servlet may be using, and using the close method
+       of the event. It is not allowed to attempt reading data from the request
+       object outside of the execution of this method.<br/>
+       On some platforms, like Windows, a client disconnect is indicated by a READ event.
+       Reading from the stream may result in -1, an IOException or an EOFException.
+       Make sure you properly handle all these three cases.
+       If you don't catch the IOException, Tomcat will instantly invoke your event chain with an ERROR as
+       it catches the error for you, and you will be notified of the error at that time.
+  </li>
+  <li>EventType.END: End may be called to end the processing of the request. Fields that have
+     been initialized in the begin method should be reset. After this event has
+     been processed, the request and response objects, as well as all their dependent
+     objects will be recycled and used to process other requests. End will also be
+     called when data is available and the end of file is reached on the request input
+     (this usually indicates the client has pipelined a request).</li>
+  <li>EventType.ERROR: Error will be called by the container in the case where an IO exception
+     or a similar unrecoverable error occurs on the connection. Fields that have
+     been initialized in the begin method should be reset. After this event has
+     been processed, the request and response objects, as well as all their dependent
+     objects will be recycled and used to process other requests.</li>
+  </ul>
+
+  <p>
+    There are some event subtypes which allow finer processing of events (note: some of these
+    events require usage of the org.apache.catalina.valves.CometConnectionManagerValve valve):
+  </p>
+
+  <ul>
+  <li>EventSubType.TIMEOUT: The connection timed out (sub type of ERROR); note that this ERROR
+    type is not fatal, and the connection will not be closed unless the servlet uses the close
+    method of the event.
+  </li>
+  <li>EventSubType.CLIENT_DISCONNECT: The client connection was closed (sub type of ERROR).
+  </li>
+  <li>EventSubType.IOEXCEPTION: An IO exception occurred, such as invalid content, for example,
+    an invalid chunk block (sub type of ERROR).
+  </li>
+  <li>EventSubType.WEBAPP_RELOAD: The web application is being reloaded (sub type of END).
+  </li>
+  <li>EventSubType.SESSION_END: The servlet ended the session (sub type of END).
+  </li>
+  </ul>
+
+  <p>
+    As described above, the typical lifecycle of a Comet request will consist in a series of
+    events such as: BEGIN -> READ -> READ -> READ -> ERROR/TIMEOUT. At any time, the servlet
+    may end processing of the request by using the close method of the event object.
+  </p>
+
+  </subsection>
+
+  <subsection name="CometFilter">
+
+  <p>
+    Similar to regular filters, a filter chain is invoked when comet events are processed.
+    These filters should implement the CometFilter interface (which works in the same way as
+    the regular Filter interface), and should be declared and mapped in the deployment
+    descriptor in the same way as a regular filter. The filter chain when processing an event
+    will only include filters which match all the usual mapping rules, and also implement
+    the CometFiler interface.
+  </p>
+
+  </subsection>
+
+  <subsection name="Example code">
+
+  <p>
+    The following pseudo code servlet implements asynchronous chat functionality using the API
+    described above:
+  </p>
+
+  <source><![CDATA[public class ChatServlet
+    extends HttpServlet implements CometProcessor {
+
+    protected ArrayList<HttpServletResponse> connections =
+        new ArrayList<HttpServletResponse>();
+    protected MessageSender messageSender = null;
+
+    public void init() throws ServletException {
+        messageSender = new MessageSender();
+        Thread messageSenderThread =
+            new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]");
+        messageSenderThread.setDaemon(true);
+        messageSenderThread.start();
+    }
+
+    public void destroy() {
+        connections.clear();
+        messageSender.stop();
+        messageSender = null;
+    }
+
+    /**
+     * Process the given Comet event.
+     *
+     * @param event The Comet event that will be processed
+     * @throws IOException
+     * @throws ServletException
+     */
+    public void event(CometEvent event)
+        throws IOException, ServletException {
+        HttpServletRequest request = event.getHttpServletRequest();
+        HttpServletResponse response = event.getHttpServletResponse();
+        if (event.getEventType() == CometEvent.EventType.BEGIN) {
+            log("Begin for session: " + request.getSession(true).getId());
+            PrintWriter writer = response.getWriter();
+            writer.println("<!DOCTYPE html>");
+            writer.println("<head><title>JSP Chat</title></head><body>");
+            writer.flush();
+            synchronized(connections) {
+                connections.add(response);
+            }
+        } else if (event.getEventType() == CometEvent.EventType.ERROR) {
+            log("Error for session: " + request.getSession(true).getId());
+            synchronized(connections) {
+                connections.remove(response);
+            }
+            event.close();
+        } else if (event.getEventType() == CometEvent.EventType.END) {
+            log("End for session: " + request.getSession(true).getId());
+            synchronized(connections) {
+                connections.remove(response);
+            }
+            PrintWriter writer = response.getWriter();
+            writer.println("</body></html>");
+            event.close();
+        } else if (event.getEventType() == CometEvent.EventType.READ) {
+            InputStream is = request.getInputStream();
+            byte[] buf = new byte[512];
+            do {
+                int n = is.read(buf); //can throw an IOException
+                if (n > 0) {
+                    log("Read " + n + " bytes: " + new String(buf, 0, n)
+                            + " for session: " + request.getSession(true).getId());
+                } else if (n < 0) {
+                    error(event, request, response);
+                    return;
+                }
+            } while (is.available() > 0);
+        }
+    }
+
+    public class MessageSender implements Runnable {
+
+        protected boolean running = true;
+        protected ArrayList<String> messages = new ArrayList<String>();
+
+        public MessageSender() {
+        }
+
+        public void stop() {
+            running = false;
+        }
+
+        /**
+         * Add message for sending.
+         */
+        public void send(String user, String message) {
+            synchronized (messages) {
+                messages.add("[" + user + "]: " + message);
+                messages.notify();
+            }
+        }
+
+        public void run() {
+
+            while (running) {
+
+                if (messages.size() == 0) {
+                    try {
+                        synchronized (messages) {
+                            messages.wait();
+                        }
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                }
+
+                synchronized (connections) {
+                    String[] pendingMessages = null;
+                    synchronized (messages) {
+                        pendingMessages = messages.toArray(new String[0]);
+                        messages.clear();
+                    }
+                    // Send any pending message on all the open connections
+                    for (int i = 0; i < connections.size(); i++) {
+                        try {
+                            PrintWriter writer = connections.get(i).getWriter();
+                            for (int j = 0; j < pendingMessages.length; j++) {
+                                writer.println(pendingMessages[j] + "<br>");
+                            }
+                            writer.flush();
+                        } catch (IOException e) {
+                            log("IOExeption sending message", e);
+                        }
+                    }
+                }
+
+            }
+
+        }
+
+    }
+
+}]]></source>
+
+  </subsection>
+  <subsection name="Comet timeouts">
+    <p>If you are using the NIO connector, you can set individual timeouts for your different comet connections.
+       To set a timeout, simply set a request attribute like the following code shows:</p>
+    <source>CometEvent event.... event.setTimeout(30*1000);</source>
+    <p>or</p>
+    <source>event.getHttpServletRequest().setAttribute("org.apache.tomcat.comet.timeout", new Integer(30 * 1000));</source>
+    <p>
+       This sets the timeout to 30 seconds.
+       Important note: in order to set this timeout, it has to be done on the <code>BEGIN</code> event.
+       The default value is <code>soTimeout</code>
+    </p>
+    <p>If you are using the APR connector, all Comet connections will have the same timeout value. It is <code>soTimeout*50</code>
+    </p>
+  </subsection>
+
   </section>
 
   <section name="Asynchronous writes">
 
   <p>
-    When using HTTP connectors (based on APR or NIO/NIO2),
-    Tomcat supports using sendfile to send large static files.
+    When APR or NIO is enabled, Tomcat supports using sendfile to send large static files.
     These writes, as soon as the system load increases, will be performed
     asynchronously in the most efficient way. Instead of sending a large response using
     blocking writes, it is possible to write content to a static file, and write it
diff --git a/webapps/docs/architecture/project.xml b/webapps/docs/architecture/project.xml
index be587e8..c1f1d6b 100644
--- a/webapps/docs/architecture/project.xml
+++ b/webapps/docs/architecture/project.xml
@@ -15,10 +15,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project name="Apache Tomcat 9 Architecture"
+<project name="Apache Tomcat 8 Architecture"
         href="http://tomcat.apache.org/">
 
-    <title>Apache Tomcat 9 Architecture</title>
+    <title>Apache Tomcat 8 Architecture</title>
 
     <logo href="/images/tomcat.gif">
       The Apache Tomcat Servlet/JSP Container
diff --git a/webapps/docs/building.xml b/webapps/docs/building.xml
index b1e2e79..9189d58 100644
--- a/webapps/docs/building.xml
+++ b/webapps/docs/building.xml
@@ -43,10 +43,10 @@
 
 </section>
 
-<section name="Download a Java Development Kit (JDK) version 8">
+<section name="Download a Java Development Kit (JDK) version 7">
 
 <p>
-Building Apache Tomcat requires a JDK (version 8) to be installed. You can download one from<br />
+Building Apache Tomcat requires a JDK (version 7) to be installed. You can download one from<br />
 <a href="http://www.oracle.com/technetwork/java/javase/downloads/index.html">http://www.oracle.com/technetwork/java/javase/downloads/index.html</a><br/>
 <a href="http://openjdk.java.net/install/index.html">http://openjdk.java.net/install/index.html</a><br/>
 or another JDK vendor.
@@ -59,17 +59,17 @@
 
 </section>
 
-<section name="Install Apache Ant 1.9.3 or later">
+<section name="Install Apache Ant 1.8.2 or later">
 
 <p>
-Download a binary distribution of Ant 1.9.3 or later from
+Download a binary distribution of Ant 1.8.2 or later from
 <a href="http://ant.apache.org/bindownload.cgi">here</a>.
 </p>
 
 <p>
 Unpack the binary distribution into a convenient location so that the
 Ant release resides in its own directory (conventionally named
-<code>apache-ant-1.9.x</code>).  For the remainder of this guide,
+<code>apache-ant-1.8.x</code>).  For the remainder of this guide,
 the symbolic name <code>${ant.home}</code> is used to refer to the full pathname of
  the Ant installation directory directory.
 </p>
@@ -87,7 +87,7 @@
 
   <p>
   Tomcat SVN repository URL:
-  <a href="http://svn.apache.org/repos/asf/tomcat/trunk/">http://svn.apache.org/repos/asf/tomcat/trunk/</a>
+  <a href="http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/">http://svn.apache.org/repos/asf/tomcat/tc8.0.x/trunk/</a>
   </p>
   <p>
   Tomcat source packages:
@@ -230,7 +230,7 @@
 <table class="defaultTable">
  <tr><td>TOMCAT_LIBS_BASE</td><td>The same location as the <code>base.path</code>
   setting in <code>build.properties</code>, where the binary dependencies have been downloaded</td></tr>
- <tr><td>ANT_HOME</td><td>the base path of Ant 1.9.3 or later</td></tr>
+ <tr><td>ANT_HOME</td><td>the base path of Ant 1.8.2 or later</td></tr>
 </table>
 
 
diff --git a/webapps/docs/changelog.xml b/webapps/docs/changelog.xml
index b688ba9..a62d87e 100644
--- a/webapps/docs/changelog.xml
+++ b/webapps/docs/changelog.xml
@@ -38,37 +38,2914 @@
 
   Fixes having an issue number are sorted by their number, ascending.
 
-  There is no ordering by add/update/fix/scode.
+  There is no ordering by add/update/fix.
 
   Other fixed issues are added to the end of the list, chronologically.
   They eventually become mixed with the numbered issues. (I.e., numbered
   issues to not "pop up" wrt. others).
 -->
-<!--
-  Note: Until the first 9.0.x release, the only entries that should appear in
-        the changelog are those are relate only to 9.0.x.
- -->
-<section name="Tomcat 9.0.0">
-  <subsection name="General">
+<section name="Tomcat 8.0.16 (markt)">
+  <subsection name="Catalina">
     <changelog>
+      <fix>
+        <bug>57172</bug>: Provide a better error message if something attempts to
+        access a resource through a web application class loader that has been
+        stopped. (markt/kkolinko)
+      </fix>
+      <fix>
+        <bug>57173</bug>: Revert the fix for <bug>56953</bug> that broke
+        annotation scanning in some cases. (markt)
+      </fix>
+      <fix>
+        <bug>57180</bug>: Do not limit the CORS filter to only accepting
+        requests that use an HTTP method defined in RFC 7231. (markt)
+      </fix>
+      <fix>
+        <bug>57190</bug>: Fix <code>ServletContext.getContext(String)</code>
+        when parallel deployment is used so that the correct ServletContext is
+        returned. (markt)
+      </fix>
+      <fix>
+        <bug>57208</bug>: Prevent NPE in JNDI Realm when no results are found
+        in a directory context for a user with specified user name. Based on
+        a patch provided by Jason McIntosh. (violetagg)
+      </fix>
       <add>
-        Make Java 8 the minimum required version to build and run Tomcat 9.
+        <bug>57209</bug>: Add a new attribute, userSearchAsUser to the JNDI
+        Realm. (markt)
+      </add>
+      <fix>
+        <bug>57215</bug>: Ensure that the result of calling
+        <code>HttpServletRequest.getContextPath()</code> is neither decoded nor
+        normalized as required by the Servlet specification. (markt)
+      </fix>
+      <fix>
+        <bug>57216</bug>: Improve handling of invalid context paths. A context
+        path should either be an empty string or start with a
+        <code>&apos;/&apos;</code> and do not end with a
+        <code>&apos;/&apos;</code>. Invalid context path are automatically
+        corrected and a warning is logged. The <code>null</code> and
+        <code>&quot;/&quot;</code> values are now correctly changed to
+        <code>&quot;&quot;</code>. (markt/kkolinko)
+      </fix>
+      <fix>
+        Update storeconfig with the CredentialHandler element. (remm)
+      </fix>
+      <fix>
+        Correct message that is logged when load-on-startup servlet fails
+        to load. It was logging a wrong name. (kkolinko)
+      </fix>
+      <fix>
+        <bug>57239</bug>: Correct several message typos. Includes patch by
+        vladk. (kkolinko)
+      </fix>
+      <fix>
+        Fix closing of Jars during annotation scanning. (schultz/kkolinko)
+      </fix>
+      <fix>
+        Fix a concurrency issue in async processing. Ensure that a non-container
+        thread can not change the async state until the container thread has
+        completed. (markt)
+      </fix>
+      <fix>
+        <bug>57252</bug>: Provide application configured error pages with a
+        chance to handle an async error before the built-in error reporting.
         (markt)
+      </fix>
+      <fix>
+        <bug>57281</bug>: Enable non-public Filter and Servlet classes to be
+        configured programmatically via the Servlet 3.0 API and then used
+        without error when running under a SecurityManager. (markt)
+      </fix>
+      <fix>
+        <bug>57308</bug>: Remove unnecessary calls to
+        <code>System.getProperty()</code> where more suitable API calls are
+        available. (markt)
+      </fix>
+      <add>
+        Add unit tests for RemoteAddrValve and RemoteHostValve. (rjung)
+      </add>
+      <add>
+        Allow to configure RemoteAddrValve and RemoteHostValve to
+        adopt behavior depending on the connector port. Implemented
+        by optionally adding the connector port to the string compared
+        with the patterns <code>allow</code> and <code>deny</code>. Configured
+        using <code>addConnectorPort</code> attribute on valve. (rjung)
+      </add>
+      <add>
+        Optionally trigger authentication instead of denial in
+        RemoteAddrValve and RemoteHostValve. This only works in
+        combination with <code>preemptiveAuthentication</code>
+        on the application context. Configured using
+        <code>invalidAuthenticationWhenDeny</code> attribute on valve. (rjung)
+      </add>
+      <fix>
+        Remove the obsolete <code>jndi</code> protocol usage from the scanning
+        process performed by StandardJarScanner. (violetagg)
+      </fix>
+      <fix>
+        Prevent file descriptors leak and ensure that files are closed after
+        retrieving the last modification time. (violetagg)
+      </fix>
+      <update>
+        Make <code>o.a.catalina.webresources.StandardRoot</code> easier for
+        extending. (violetagg)
+      </update>
+      <fix>
+        <bug>57326</bug>: Enable <code>AsyncListener</code> implementations to
+        re-register themselves during <code>AsyncListener.onStartAsync</code>.
+        (markt)
+      </fix>
+      <fix>
+        <bug>57331</bug>: Allow ExpiresFilter to use "year" as synonym for
+        "years" in its configuration. (kkolinko)
+      </fix>
+      <fix>
+        Ensure that if the RewriteValve rewrites a request that subsequent calls
+        to <code>HttpServletRequest.getRequestURI()</code> return the undecoded
+        URI. (markt)
+      </fix>
+      <fix>
+        Ensure that if the RewriteValve rewrites a request to a non-normalized
+        URI that the URI is normalized before the URI is mapped to ensure that
+        the correct mapping is applied. (markt)
+      </fix>
+      <fix>
+        Prevent NPEs being logged during post-processing for requests that have
+        been re-written by the RewriteValve. (markt)
+      </fix>
+      <fix>
+        Various StoreConfig improvements including removing a dependency on the
+        <code>StandardServer</code> implementation, improve consistency of
+        behaviour when MBean is not registered and improve error messages when
+        accessed via the Manager application. (markt)
+      </fix>
+      <update>
+          Improve SnoopServlet in unit tests. (rjung)
+      </update>
+      <add>
+          Add RequestDescriptor class to unit tests.
+          Adjust TestRewriteValve to use RequestDescriptor. (rjung)
       </add>
       <update>
-        Remove support for Comet. (markt)
+          Add more AJP unit tests. (rjung)
       </update>
+      <fix>
+        <bug>57363</bug>: Log to stderr if LogManager is unable to read
+        configuration files rather than swallowing the exception silently.
+        (markt)
+      </fix>
     </changelog>
   </subsection>
   <subsection name="Coyote">
     <changelog>
+      <fix>
+        Allow HTTP upgrade process to complete without data corruption when
+        additional content is sent along with the upgrade header. (remm)
+      </fix>
+      <fix>
+        <bug>57187</bug>: Regression handling the special * URL. (remm)
+      </fix>
+      <fix>
+        <bug>57234</bug>: Make SSL protocol filtering to remove insecure
+        protocols case insensitive. (markt)
+      </fix>
+      <fix>
+        <bug>57265</bug>: Fix some potential concurrency issues with sendFile
+        and the NIO connector. (markt)
+      </fix>
+      <fix>
+        <bug>57324</bug>: If the client uses <code>Expect: 100-continue</code>
+        and Tomcat responds with a non-2xx response code, Tomcat also closes the
+        connection. If Tomcat knows the connection is going to be closed when
+        committing the response, Tomcat will now also send the
+        <code>Connection: close</code> response header. (markt)
+      </fix>
+      <fix>
+        <bug>57340</bug>: When using Comet, ensure that Socket and SocketWrapper
+        are only returned to their respective caches once on socket close (it is
+        possible for multiple threads to call close concurrently). (markt)
+      </fix>
+      <fix>
+        <bug>57347</bug>: AJP response contains wrong status reason phrase
+        (rjung)
+      </fix>
+      <add>
+        <bug>57391</bug>: Allow TLS Session Tickets to be disabled when using
+        the APR/native HTTP connector. Patch provided by Josiah Purtlebaugh.
+        (markt)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>57142</bug>: As per the clarification from the JSP specification
+        maintenance lead, classes and packages imported via the page directive
+        must be made available to the EL environment via the ImportHandler.
+        (markt)
+      </fix>
+      <fix>
+        <bug>57247</bug>: Correct the default Java source and target versions in
+        the JspC usage message to <code>1.7</code> for Java 7. (markt)
+      </fix>
+      <fix>
+        <bug>57309</bug>: Ensure that the current EL Resolver is given an
+        opportunity to perform type coercion before applying the default EL
+        coercion rules. (markt)
+      </fix>
+      <fix>
+        Improve the calculation of the resource's last-modified, performed by
+        JspCompilationContext, in a way to support URLs with protocol different
+        than <code>jar:file</code>. (violetagg)
+      </fix>
+      <fix>
+        Fix potential issue with BeanELResolver when running under a security
+        manager. Some classes may not be accessible but may have accessible
+        interfaces. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <fix>
+        In order to enable define in <code>Cluster</code> element,
+        <code>ClusterSingleSignOn</code> implements <code>ClusterValve</code>.
+        (kfujino)
+      </fix>
+      <fix>
+        <bug>57338</bug>: Improve the ability of the
+        <code>ClusterSingleSignOn</code> valve to handle nodes being added and
+        removed from the Cluser at run time. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        Correct multiple issues with the flushing of batched messages that could
+        lead to duplicate and/or corrupt messages. (markt)
+      </fix>
+      <fix>
+        Correctly implement headers case insensitivity. (markt/remm)
+      </fix>
+      <fix>
+        Allow optional use of user extensions. (remm)
+      </fix>
+      <fix>
+        Allow using partial binary message handlers. (remm)
+      </fix>
+      <fix>
+        Limit ping/pong message size. (remm)
+      </fix>
+      <fix>
+        Allow configuration of the time interval for the periodic event. (remm)
+      </fix>
+      <fix>
+        More accurate annotations processing. (remm)
+      </fix>
+      <fix>
+        Allow optional default for origin header in the client. (remm)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Update documentation for CGI servlet. Recommend to copy the servlet
+        declaration into web application instead of enabling it globally.
+        Correct documentation for cgiPathPrefix. (kkolinko)
+      </fix>
       <update>
-        Remove support for the HTTP BIO and AJP BIO connectors. (markt)
+        Improve HTML version of build instructions and align with
+        BUILDING.txt. (kkolinko)
+      </update>
+      <update>
+        Improve Tomcat Manager documentation. Rearrange, add section on
+        HTML GUI, document /expire command and Server Status page. (kkolinko)
+      </update>
+      <update>
+        <bug>57238</bug>: Update information on SSL/TLS on Security and SSL
+        documentation pages. Patch by Glen Peterson. (kkolinko)
+      </update>
+      <fix>
+        <bug>57245</bug>: Correct the reference to <code>allowLinking</code> in
+        the security configuration guide since that attribute has moved from the
+        Context element to the nested Resources element. (markt)
+      </fix>
+      <fix>
+        Fix ambiguity of section links on Valves configuration reference page.
+        (kkolinko)
+      </fix>
+      <fix>
+        <bug>57261</bug>: Fix vminfo and threaddump Manager commands to start
+        their output with an "OK" line. Document them. Based on a patch by
+        Oleg Trokhov. (kkolinko)
+      </fix>
+      <fix>
+        <bug>57267</bug>: Document the <code>StoreConfigLifecycleListener</code>
+        and the <code>/save</code> command for the Manager application. (markt)
+      </fix>
+      <fix>
+        <bug>57323</bug>: Correct display of outdated sessions in sessions
+        count listing in Manager application. (kkolinko)
+      </fix>
+      <add>
+        Add document of <code>ClusterSingleSignOn</code>. (kfujino)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <update>
+        When downloading required libraries at build time, use random name
+        for temporary file and automatically create destination directory
+        (<code>base.path</code>). (kkolinko)
+      </update>
+      <update>
+        Update optional Checkstyle library to 6.2. (kkolinko)
+      </update>
+      <update>
+        Simplify <code>setproxy</code> task in <code>build.xml</code>.
+        Taskdef there is not needed since Ant 1.8.2. (kkolinko)
+      </update>
+      <fix>
+        Update "ide-eclipse" target in <code>build.xml</code> to create Eclipse
+        project that uses Java 7 compliance settings instead of workspace-wide
+        defaults. (kkolinko)
+      </fix>
+     <fix>
+        Update the package renamed copy of Apache Commons Pool 2 to the 2.3
+        release to pick up various fixes since the 2.2 release including one for
+        a possible infinite loop. (markt)
+      </fix>
+      <fix>
+        <bug>57285</bug>: Restore the manifest entry that marks the Windows
+        uninstaller application as requiring elevated privileges. (markt)
+      </fix>
+      <add>
+        <bug>57344</bug>: Provide sha1 checksum files for Tomcat downloads.
+        Correct filename patterns for apache-tomcat-*-embed.tar.gz archive
+        to exclude an *.asc file. (kkolinko)
+      </add>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.15 (markt)" rtext="2014-11-07">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        <bug>43548</bug>: Add an XML schema for the tomcat-users.xml file.
+        (markt)
+      </add>
+      <add>
+        <bug>43682</bug>: Add support for referring to the current context, host
+        and service name in per Context logging.properties files by using the
+        properties <code>${classloader.webappName}</code>,
+        <code>${classloader.hostName}</code> and
+        <code>${classloader.serviceName}</code>. (markt)
+      </add>
+      <add>
+        <bug>47919</bug>: Extend the information logged when Tomcat starts to
+        optionally log the values of command line arguments (enabled by
+        default) and environment variables (disabled by default). Note that
+        the values added to CATALINA_OPTS and JAVA_OPTS environment variables
+        will be logged, as they are used to build up the command line. (markt)
+      </add>
+      <add>
+        <bug>49939</bug>: Expose the method that clears the static resource
+        cache for a web application via JMX. (markt)
+      </add>
+      <fix>
+        <bug>55951</bug>: Allow cookies to use UTF-8 encoded values in HTTP
+        headers. This requires the use of the RFC6265
+        <strong>CookieProcessor</strong>. (markt)
+      </fix>
+      <fix>
+        <bug>55984</bug>: Using the allow separators in version 0 cookies option
+        with the legacy cookie processor should only apply to version 0 cookies.
+        Version 1 cookies with values that contain separators should not be
+        affected and should continue to be quoted. (markt)
+      </fix>
+      <add>
+        <bug>56393</bug>: Add support for RFC6265 cookie parsing and generation.
+        This is currently disabled by default and may be enabled via the
+        <strong>CookieProcessor</strong> element of a <strong>Context</strong>.
+        (markt)
+      </add>
+      <add>
+        <bug>56394</bug>: Introduce new configuration element CookieProcessor in
+        Context to allow context-specific configuration of cookie processing
+        options. Attributes of Context element that were added in Tomcat 8.0.13
+        to allow configuration of a new experimental RFC6265 based cookie parser
+        (<code>useRfc6265</code> and <code>cookieEncoding</code>) are
+        replaced by this new configuration element. (markt)
+      </add>
+      <fix>
+        Improve the previous fix for <bug>56401</bug>. Avoid logging version
+        information in the constructor since it then gets logged at undesirable
+        times such as when using <code>StoreConfig</code>. (markt)
+      </fix>
+      <fix>
+        <bug>56403</bug>: Add pluggable password derivation support to the
+        Realms via the new <code>CredentialHandler</code> interface.
+        (markt/schultz)
+      </fix>
+      <fix>
+        <bug>57016</bug>: When using the <code>PersistentValve</code> do not
+        remove sessions from the store when persisting them. (markt)
+      </fix>
+      <add>
+        Deprecate the use of system proprties to control cookie parsing and
+        replace them with attributes on the new <code>CookieProcessor</code>
+        that may be configured on a per context basis. (markt)
+      </add>
+      <fix>
+        Correct an edge case and allow a cookie if the value starts with an
+        equals character and the <code>CookieProcessor</code> is not configured
+        to allow equals characters in cookie values but is configured to allow
+        name only cookies. (markt)
+      </fix>
+      <fix>
+        <bug>57022</bug>: Ensure SPNEGO authentication continues to work with
+        the JNDI Realm using delegated credentials with recent Oracle JREs.
+        (markt)
+      </fix>
+      <fix>
+        <bug>57027</bug>: Add additional validation for stored credentials used
+        by Realms when the credential is stored using hex encoding. (markt)
+      </fix>
+      <fix>
+        <bug>57038</bug>: Add a <code>WebResource.getCodeBase()</code> method,
+        implement for all <code>WebResource</code> implementations and then use
+        it in the web application class loader to set the correct code base for
+        resources loaded from JARs and WARs. (markt)
+      </fix>
+      <fix>
+        Correct a couple of NPEs in the JNDI Realm that could be triggered with
+        when not specifying a roleBase and enabling roleSearchAsUser. (markt)
+      </fix>
+      <fix>
+        Correctly handle relative values for the docBase attribute of a Context.
+        (markt)
+      </fix>
+      <fix>
+        Ensure that log messages generated by the web application class loader
+        correctly identify the associated Context when multiple versions of a
+        Context with the same path are present. (markt)
+      </fix>
+      <fix>
+        Remove the unnecessary registration of context.xml as a redeploy
+        resource. The context.xml having an external docBase has already been
+        registered as a redeploy resource at first. (kfujino)
+      </fix>
+      <fix>
+        <bug>57089</bug>: Ensure that configuration of a session ID generator is
+        not lost when a web application is reloaded. (markt)
+      </fix>
+      <fix>
+        <bug>57105</bug>: When parsing web.xml do not limit the buffer element
+        of the jsp-property-group element to integer values as the allowed
+        values are <code>&lt;number&gt;kb</code> or <code>none</code>. (markt)
+      </fix>
+      <update>
+        Update the minimum required version of the Tomcat Native library (if
+        used) to 1.1.32. (markt)
+      </update>
+      <fix>
+        Update storeconfig with newly introduced elements: SessionIdGenerator,
+        CookieProcessor, JarScanner and JarScanFilter. (remm)
+      </fix>
+      <fix>
+        Throw a <code>NullPointerException</code> if a null string is passed to
+        the <code>write(String,int,int)</code> method of the
+        <code>PrintWriter</code> obtained from the <code>ServletResponse</code>.
+        (markt)
+      </fix>
+      <fix>
+        Cookie rewrite flag abbreviation should be CO rather than C. (remm)
+      </fix>
+      <fix>
+        <bug>57153</bug>: When the StandardJarScanner is configured to scan the
+        full class path, ensure that class path entries added directly to the
+        web application class loader are scanned. (markt)
+      </fix>
+      <fix>
+        AsyncContext should remain usable until fireOnComplete is called. (remm)
+      </fix>
+      <fix>
+        AsyncContext createListener should wrap any instantiation exception
+        using a ServletException. (remm)
+      </fix>
+      <fix>
+        <bug>57155</bug>: Allow a web application to be configured that does not
+        have a docBase on the file system. This is primarily intended for use
+        when embedding. (markt)
+      </fix>
+      <fix>
+        Propagate header ordering from fileupload to the part implementation.
+        (remm)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <add>
+        <bug>53952</bug>: Add support for TLSv1.1 and TLSv1.2 for APR connector.
+        Based upon a patch by Marcel &#352;ebek. This feature requires
+        Tomcat Native library 1.1.32 or later. (schultz/jfclere)
+      </add>
+      <scode>
+        Cache the <code>Encoder</code> instances used to convert Strings to byte
+        arrays in the Connectors (e.g. when writing HTTP headers) to improve
+        throughput. (markt)
+      </scode>
+      <add>
+        Disable SSLv3 by default for JSSE based HTTPS connectors (BIO, NIO and
+        NIO2). The change also ensures that SSLv2 is disabled for these
+        connectors although SSLv2 should already be disabled by default by the
+        JRE. (markt)
+      </add>
+      <add>
+        Disable SSLv3 by default for the APR/native HTTPS connector. (markt)
+      </add>
+      <fix>
+        Do not increase remaining counter at end of stream in
+        IdentityInputFilter. (kkolinko)
+      </fix>
+      <fix>
+        Trigger an error if an invalid attempt is made to use non-blocking IO.
+        (markt)
+      </fix>
+      <fix>
+        <bug>57157</bug>: Allow calls to
+        <code>AsyncContext.start(Runnable)</code> during non-blocking IO reads
+        and writes. (markt)
+      </fix>
+      <fix>
+        Async state MUST_COMPLETE should still be started. (remm)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>57099</bug>: Ensure that semi-colons are not permitted in JSP
+        import page directives. (markt)
+      </fix>
+      <fix>
+        <bug>57113</bug>: Fix broken package imports in Expression Language when
+        more than one package was imported and the desired class was not in the
+        last package imported. (markt)
+      </fix>
+      <fix>
+        <bug>57132</bug>: Fix import conflicts reporting in Expression Language.
+        (kkolinko)
+      </fix>
+      <fix>
+        When coercing an object to a given type, only attempt coercion to an
+        array if both the object type and the target type are an array type.
+        (violetagg/markt)
+      </fix>
+      <fix>
+        Improve handling of invalid input to
+        <code>javax.el.ImportHandler.resolveClass()</code>. (markt)
+      </fix>
+      <fix>
+        Allow the same class to be added to an instance of
+        <code>javax.el.ImportHandler</code> more than once without triggering
+        an error. The second and subsequent calls for the same class will be
+        ignored. (markt)
+      </fix>
+      <fix>
+        <bug>57136</bug>: Ensure only <code>\${</code> and <code>\#{</code> are
+        treated as escapes for <code>${</code> and <code>#{</code> rather than
+        <code>\$</code> and <code>\#</code> being treated as escapes for
+        <code>$</code> and <code>#</code> when processing literal expressions in
+        expression language. (markt)
+      </fix>
+      <fix>
+        When coercing an object to an array type in Expression Language, handle
+        the case where the source object is an array of primitives.
+        (markt/kkolinko)
+      </fix>
+      <fix>
+        Do not throw an exception on missing JSP file servlet initialization.
+        (remm)
+      </fix>
+      <fix>
+        <bug>57148</bug>: When coercing an object to a given type and a
+        <code>PropertyEditor</code> has been registered for the type correctly
+        coerce the empty string to <code>null</code> if the
+        <code>PropertyEditor</code> throws an exception. (kkolinko/markt)
+      </fix>
+      <fix>
+        <bug>57153</bug>: Correctly scan for TLDs located in directories that
+        represent exanded JARs files that have been added to the web application
+        class loader&apos;s class path. (markt)
+      </fix>
+      <fix>
+        <bug>57141</bug>: Enable EL in JSPs to refer to static fields of
+        imported classes including the standard <code>java.lang.*</code>
+        imports. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <fix>
+        Add support for the <code>SessionIdGenerator</code> to cluster manager
+        template. (kfujino)
+      </fix>
+      <fix>
+        Avoid possible integer overflows reported by Coverity Scan. (fschumacher)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        <bug>57054</bug>: Correctly handle the case in the WebSocket client
+        when the HTTP response to the upgrade request can not be read in a
+        single pass; either because the buffer is too small or the server sent
+        the response in multiple packets. (markt)
+      </fix>
+      <add>
+        Extend support for the <code>permessage-deflate</code> extension to the
+        client implementation. (markt)
+      </add>
+      <fix>
+        Fix client subprotocol handling. (remm)
+      </fix>
+      <fix>
+        Add null checks for arguments in remote endpoint. (remm/kkolinko)
+      </fix>
+      <fix>
+        <bug>57091</bug>: Work around the behaviour of the Oracle JRE when
+        creating new threads in an applet environment that breaks the WebSocket
+        client implementation. Patch provided by Niklas Hallqvist. (markt)
+      </fix>
+      <fix>
+        <bug>57118</bug>: Ensure that that an <code>EncodeException</code> is
+        thrown by <code>RemoteEndpoint.Basic.sendObject(Object)</code> rather
+        than an <code>IOException</code> when no suitable <code>Encoder</code>
+        is configured for the given Object. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Correct a couple of broken links in the Javadoc. (markt)
+      </fix>
+      <fix>
+        Correct documentation for <code>ServerCookie.ALLOW_NAME_ONLY</code>
+        system property. (kkolinko)
+      </fix>
+      <fix>
+        <bug>57049</bug>: Clarified that <code>jvmRoute</code> can be set in
+        <code>&lt;Engine&gt;</code>'s <code>jvmRoute</code> or in a system
+        property. (schultz)
+      </fix>
+      <fix>
+        Correct version of Java WebSocket mentioned in documentation
+        (s/1.0/1.1/). (markt/kkolinko)
+      </fix>
+      <update>
+        Suppress timestamp comments in Javadoc. (kkolinko)
+      </update>
+      <fix>
+        <bug>57147</bug>: Various corrections to the JDBC Store section of the
+        session manager configuration page of the documentation web application.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Tribes">
+    <changelog>
+      <fix>
+        <bug>45282</bug>: Improve shutdown of NIO receiver so that sockets are
+        closed cleanly. (fhanik/markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="jdbc-pool">
+    <changelog>
+      <fix>
+        <bug>57005</bug>: Fix javadoc errors when building with Java 8. Patch
+        provided by Pierre Viret. (markt)
+      </fix>
+      <fix>
+        <bug>57079</bug>: Use Tomcat version number for jdbc-pool module when
+        building and shipping the module as part of Tomcat. (markt)
+      </fix>
+      <fix>
+        Fix broken overview page in javadoc generated via "javadoc" task in
+        jdbc-pool build.xml file. (kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        <bug>56079</bug>: The uninstaller packaged with the Apache Tomcat
+        Windows installer is now digitally signed. (markt)
+      </fix>
+      <fix>
+        Fix timestamps in Tomcat build and jdbc-pool to use 24-hour format
+        instead of 12-hour one and use UTC timezone. (markt/kkolinko)
+      </fix>
+      <fix>
+        Update the package renamed copy of Apache Commons DBCP 2 to revision
+        1631450 to pick up additional fixes since the 2.0.1 release including
+        Javadoc corrections to fix errors when compiling with Java 8. (markt)
+      </fix>
+      <update>
+        <bug>56596</bug>: Update to Tomcat Native Library version 1.1.32 to
+        pick up the Windows binaries that are based on OpenSSL 1.0.1j and APR
+        1.5.1. (markt)
       </update>
       <scode>
-        Refactor HTTP upgrade and AJP implementations to reduce duplication.
+        In Tomcat tests: log name of the current test method at start time.
+        (kkolinko)
+      </scode>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.14 (markt)" rtext="2014-09-29">
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        <bug>56079</bug>: The Apache Tomcat Windows installer, the Apache Tomcat
+        Windows service and the Apache Tomcat Windows service monitor
+        application are now digitally signed. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.13 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>55917</bug>: Allow bytes in the range 0x80 to 0xFF to appear in
+        cookie values if the cookie is a V1 (RFC2109) cookie and the value is
+        correctly quoted. The new RFC6265 based cookie parser must be enabled to
+        correctly handle these cookies. (markt)
+      </fix>
+      <fix>
+        <bug>55918</bug>: Do not permit control characters to appear in quoted
+        V1 (RFC2109) cookie values. The new RFC6265 based cookie parser must be
+        enabled to correctly handle these cookies. (markt)
+      </fix>
+      <fix>
+        <bug>55921</bug>: Correctly handle (ignore the cookie) unescaped JSON in
+        a cookie value. The new RFC6265 based cookie parser must be enabled to
+        correctly handle these cookies. (markt)
+      </fix>
+      <add>
+        <bug>56401</bug>: Log version information when Tomcat starts.
+        (markt/kkolinko)
+      </add>
+      <add>
+        <bug>56530</bug>: Add a web application class loader implementation that
+        supports the parallel loading of web application classes. (markt)
+      </add>
+      <fix>
+        <bug>56900</bug>: Fix some potential resource leaks when reading
+        property files reported by Coverity Scan. Based on patches provided by
+        Felix Schumacher. (markt)
+      </fix>
+      <fix>
+        <bug>56902</bug>: Fix a potential resource leak in the Default Servlet
+        reported by Coverity Scan. Based on a patch provided by Felix
+        Schumacher. (markt)
+      </fix>
+      <fix>
+        <bug>56903</bug>: Correct the return value for
+        <code>StandardContext.getResourceOnlyServlets()</code> so that multiple
+        names are separated by commas. Identified by Coverity Scan and fixed
+        based on a patch by Felix Schumacher. (markt)
+      </fix>
+      <add>
+        Add an additional implementation of a RFC6265 based cookie parser along
+        with new Context options to select and configure it. This parser is
+        currently considered experimental and is not used by default. (markt)
+      </add>
+      <fix>
+        Fixed the multipart elements merge operation performed during web
+        application deployment. Identified by Coverity Scan. (violetagg)
+      </fix>
+      <fix>
+        Correct the information written by
+        <code>ExtendedAccessLogValve</code> when a format token x-O(XXX) is
+        used so that multiple values for a header XXX are separated by commas.
+        Identified by Coverity Scan. (violetagg)
+      </fix>
+      <fix>
+        Fix a potential resource leak when reading MANIFEST.MF file for
+        extension dependencies reported by Coverity Scan. (violetagg)
+      </fix>
+      <fix>
+        Fix some potential resource leaks when reading properties, files and
+        other resources. Reported by Coverity Scan. (violetagg)
+      </fix>
+      <fix>
+        Correct the previous fix for <bug>56825</bug> that enabled pre-emptive
+        authentication to work with the SSL authenticator. (markt)
+      </fix>
+      <scode>
+        Refactor to reduce code duplication identified by Simian. (markt)
+      </scode>
+      <fix>
+        When using parallel deployment and <code>undeployOldVersions</code>
+        feature is enabled on a Host, correctly undeploy context of old
+        version. Make sure that Tomcat does not undeploy older Context if
+        current context is not running. (kfujino)
+      </fix>
+      <fix>
+        Fix a rare threading issue when locking resources via WebDAV.
+        (markt)
+      </fix>
+      <fix>
+        Fix a rare threading issue when using HTTP digest authentication.
+        (markt)
+      </fix>
+      <fix>
+        When deploying war, add XML file in the config base to the redeploy
+        resources if war does not have META-INF/context.xml or
+        <code>deployXML</code> is false. If  XML file is created in the config
+        base, redeploy will occur. (kfujino)
+      </fix>
+      <scode>
+        Various changes to reduce unnecessary code in Tomcat&apos;s copy of
+        Apache Commons BCEL to reduce the time taken for annotation scanning
+        when web applications start. Includes contributions from kkolinko and
+        hzhang9. (markt)
+      </scode>
+      <fix>
+        <bug>56938</bug>: Ensure web applications that have mixed case context
+        paths and are deployed as directories are correctly removed on undeploy
+        when running on a case sensitive file system. (markt)
+      </fix>
+      <add>
+        <bug>57004</bug>: Add <code>stuckThreadCount</code> property to
+        <code>StuckThreadDetectionValve</code>'s JMX bean. Patch provided by
+        Ji&#x159;&#xED; Pejchal. (schultz)
+      </add>
+      <fix>
+        <bug>57011</bug>: Ensure that the request and response are correctly
+        recycled when processing errors during async processing. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        <bug>56910</bug>: Prevent the invalid value of <code>-1</code> being
+        used for <code>maxConnections</code> with APR connectors. (markt)
+      </fix>
+      <fix>
+        Ensure that AJP connectors enable the <code>KeepAliveTimeout</code>.
+        (kfujino)
+      </fix>
+      <fix>
+        Reduce duplicated code. All AJP connectors use common method to
+        configuration of processor. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>43001</bug>: Enable the JspC Ant task to set the JspC option
+        <code>mappedFile</code>. (markt)
+      </fix>
+      <fix>
+        Ensure that the implementation of
+        <code>javax.servlet.jsp.PageContext.include(String)</code>
+        and
+        <code>javax.servlet.jsp.PageContext.include(String, boolean)</code>
+        will throw <code>IOException</code> when an I/O error occur during
+        the operation. (violetagg)
+      </fix>
+      <fix>
+        <bug>56908</bug>: Fix some potential resource leaks when reading
+        jar files. Reported by Coverity Scan. Patch provided by Felix
+        Schumacher. (violetagg)
+      </fix>
+      <fix>
+        Fix a potential resource leak in JDTCompiler when checking wether
+        a resource is a package. Reported by Coverity Scan. (fschumacher)
+      </fix>
+      <fix>
+        <bug>56991</bug>: Deprecate the use of a request attribute to pass a
+        &lt;jsp-file&gt; declaration to Jasper and prevent an infinite loop
+        if this technique is used in conjunction with an include. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        <bug>56905</bug>: Make destruction on web application stop of thread
+        group used for WebSocket connections more robust. (kkolinko/markt)
+      </fix>
+      <fix>
+        <bug>56907</bug>: Ensure that client IO threads are stopped if a secure
+        WebSocket client connection fails. (markt)
+      </fix>
+      <fix>
+        <bug>56982</bug>: Return the actual negotiated extensions rather than an
+        empty list for <code>Session.getNegotiatedExtensions()</code>. (markt)
+      </fix>
+      <update>
+        Update the WebSocket implementation to support the Java WebSocket
+        specification version 1.1. (markt)
+      </update>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <add>
+        Add <code>JarScanner</code> to the nested components listed for a
+        Context. (markt)
+      </add>
+      <update>
+        Update the Windows authentication documentation after some additional
+        testing to answer the remaining questions. (markt)
+      </update>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        <bug>56895</bug>: Correctly compose <code>JAVA_OPTS</code> in
+        <code>catalina.bat</code> so that escape sequences are preserved. Patch
+        by Lucas Theisen. (markt)
+      </fix>
+      <update>
+        <bug>56988</bug>: Allow to use relative path in <code>base.path</code>
+        setting when building Tomcat. (kkolinko)
+      </update>
+      <fix>
+        <bug>56990</bug>: Ensure that the <code>ide-eclipse</code> build target
+        downloads all the libraries required by the default Eclipse
+        configuration files. (markt)
+      </fix>
+      <fix>
+        Update the package renamed copy of Apache Commons DBCP 2 to revision
+        1626988 to pick up the fixes since the 2.0.1 release including support
+        for custom eviction policies. (markt)
+      </fix>
+      <fix>
+        Update the package renamed copy of Apache Commons Pool 2 to revision
+        1627271 to pick up the fixes since the 2.2 release including some memory
+        leak fixes and support for application provided eviction policies.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.12 (markt)" rtext="2014-09-03">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        Make the session id generator extensible by adding a
+        <code>SessionIdGenerator</code> interface, an abstract
+        base class and a standard implementation. (rjung)
+      </add>
+      <fix>
+        <bug>56882</bug>: Fix regression in processing of includes and forwards
+        when Context have been reloaded. Tomcat was responding with HTTP Status
+        503 (Servlet xxx is currently unavailable). (kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        When building a list of JSSE ciphers from an OpenSSL cipher defintiion,
+        ignore unknown criteria rather than throwing a
+        <code>NullPointerException</code>. (markt)
+      </fix>
+      <add>
+        Add support for the EECDH alias when using the OpenSSL cipher syntax to
+        define JSSE ciphers. (markt)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        Correct a logic error in the <code>JasperElResolver</code>. There was no
+        functional impact but the code was less efficient as a result of the
+        error. Based on a patch by martinschaef. (markt)
+      </fix>
+      <fix>
+        <bug>56568</bug>: Enable any HTTP method to be used to request a JSP
+        page that has the <code>isErrorPage</code> page directive set to
+        <code>true</code>. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <add>
+        Extend support for the <code>permessage-deflate</code> extension to
+        compression of outgoing messages on the server side. (markt)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <add>
+        <bug>56323</bug>: Include the <code>*.bat</code> files when installing
+        Tomcat via the Windows installer. (markt)
+      </add>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.11 (markt)" rtext="2014-08-22">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>56658</bug>: Fix regression that a context was inaccessible after
+        reload. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56710</bug>: Do not map requests to servlets when context is
+        being reloaded. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56712</bug>: Fix session idle time calculations in
+        <code>PersistenceManager</code>. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56717</bug>: Fix duplicate registration of
+        <code>MapperListener</code> during repeated starts of embedded Tomcat.
+        (kkolinko)
+      </fix>
+      <add>
+        <bug>56724</bug>: Write an error message to Tomcat logs if container
+        background thread is aborted unexpectedly. (kkolinko)
+      </add>
+      <fix>
+        When scanning class files (e.g. for annotations) and reading the number
+        of parameters in a <code>MethodParameters</code> structure only read a
+        single byte (rather than two bytes) as per the JVM specification. Patch
+        provided by Francesco Komauli. (markt)
+      </fix>
+      <fix>
+        Allow the JNDI Realm to start even if the directory is not available.
+        The directory not being available is not fatal once the Realm is started
+        and it need not be fatal when the Realm starts. Based on a patch by
+        Cédric Couralet. (markt)
+      </fix>
+      <fix>
+        <bug>56736</bug>: Avoid an incorrect <code>IllegalStateException</code>
+        if the async timeout fires after a non-container thread has called
+        <code>AsyncContext.dispatch()</code> but before a container thread
+        starts processing the dispatch. (markt)
+      </fix>
+      <fix>
+        <bug>56739</bug>: If an application handles an error on an application
+        thread during asynchronous processing by calling
+        <code>HttpServletResponse.sendError()</code>, then ensure that the
+        application is given an opportunity to report that error via an
+        appropriate application defined error page if one is configured. (markt)
+      </fix>
+      <fix>
+        <bug>56784</bug>: Fix a couple of rare but theoretically possible
+        atomicity bugs. (markt)
+      </fix>
+      <fix>
+        <bug>56785</bug>: Avoid <code>NullPointerException</code> if directory
+        exists on the class path that is not readable by the Tomcat user.
+        (markt)
+      </fix>
+      <fix>
+        <bug>56796</bug>: Remove unnecessary sleep when stopping a web
+        application. (markt)
+      </fix>
+      <fix>
+        <bug>56801</bug>: Improve performance of
+        <code>org.apache.tomcat.util.file.Matcher</code> which is to filter JARs
+        for scanning during web application start. Based on a patch by Sheldon
+        Shao. (markt)
+      </fix>
+      <fix>
+        <bug>56815</bug>: When the <code>gzip</code> option is enabled for the
+        <code>DefaultServlet</code> ensure that a suitable <code>Vary</code>
+        header is returned for resources that might be returned directly in
+        compressed form. (markt)
+      </fix>
+      <fix>
+        Do not mark threads from the container thread pool as container threads
+        when being used to process <code>AsyncContext.start(Runnable)</code> so
+        processing is correctly transferred back to a genuine container thread
+        when necessary. (markt)
+      </fix>
+      <add>
+        Add simple caching for calls to <code>StandardRoot.getResources()</code>
+        in the new (for 8.0.x) resources implementation. (markt)
+      </add>
+      <fix>
+        <bug>56825</bug>: Enable pre-emptive authentication to work with the
+        SSL authenticator. Based on a patch by jlmonteiro. (markt)
+      </fix>
+      <fix>
+        <bug>56840</bug>: Avoid NPE when the rewrite valve is mapped to
+        a context. (remm)
+      </fix>
+      <fix>
+        Correctly handle multiple <code>accept-language</code> headers rather
+        than just using the first header to determine the user&apos;s preferred
+        Locale. (markt)
+      </fix>
+      <fix>
+        <bug>56848</bug>: Improve handling of <code>accept-language</code>
+        headers. (markt)
+      </fix>
+      <fix>
+        <bug>56857</bug>: Fix thread safety issue when calling ServletContext
+        methods while running under a security manager. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Fix NIO2 sendfile state tracking and error handling to fix
+        various corruption issues. (remm)
+      </fix>
+      <fix>
+        Missing timeout for NIO2 sendfile writes. (remm)
+      </fix>
+      <fix>
+        Allow inline processing for NIO2 sendfile and optimize keepalive
+        behavior. (remm)
+      </fix>
+      <fix>
+        Fix excessive NIO2 sendfile direct memory use in some cases, sendfile
+        will now instead use the regular socket write buffer as configured.
+        (remm)
+      </fix>
+      <fix>
+        <bug>56661</bug>: Fix <code>getLocalAddr()</code> for AJP connectors.
+        The complete fix is only available with a recent AJP forwarder like
+        the forthcoming mod_jk 1.2.41. (rjung)
+      </fix>
+      <fix>
+        Use default ciphers defined as
+        <code>HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5</code> so
+        that no weak ciphers are enabled by default. (remm)
+      </fix>
+      <fix>
+        <bug>56780</bug>: Enable Tomcat to start when using SSL with an IBM JRE
+        in strict SP800-131a mode. (markt)
+      </fix>
+      <fix>
+        <bug>56810</bug>: Remove use of Java 8 specific API calls in unit tests
+        for OpenSSL to JSSE cipher conversion. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>56709</bug>: Fix system property name in a log message. Submitted
+        by Robert Kish. (remm)
+      </fix>
+      <fix>
+        <bug>56797</bug>: When matching a method in an EL expression, do not
+        treat bridge methods as duplicates of the method they bridge to. In this
+        case always call the target of the bridge method. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        <bug>56746</bug>: Allow secure WebSocket client threads to use the
+        current context class loader rather than explicitly setting it to the
+        class loader that loaded the WebSocket implementation. This allows
+        WebSocket client connections from within web applications to access,
+        amongst other things, the JNDI resources associated with the web
+        application. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Correct the label in the list of sessions by idle time for the bin that
+        represents the idle time immediately below the maximum permitted idle
+        time when using the expire command of the Manager application. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="jdbc-pool">
+    <changelog>
+      <fix>
+        <bug>53088</bug>: More identifiable thread name. (fhanik)
+      </fix>
+      <fix>
+        <bug>53200</bug>: Selective logging for slow versus failed queries.
+        (fhanik)
+      </fix>
+      <fix>
+        <bug>53853</bug>: More flexible classloading. (fhanik)
+      </fix>
+      <fix>
+        <bug>54225</bug>: Disallow empty init SQL. (fhanik)
+      </fix>
+      <fix>
+        <bug>54227</bug>: Evaluate max age upon borrow. (fhanik)
+      </fix>
+      <fix>
+        <bug>54235</bug>: Disallow nested pools exploitating using data source.
+        (fhanik)
+      </fix>
+      <fix>
+        <bug>54395</bug>: Fix JDBC interceptor parsing bug. (fhanik)
+      </fix>
+      <fix>
+        <bug>54537</bug>: Performance improvement in
+        <code>StatementFinalizer</code>. (fhanik)
+      </fix>
+      <fix>
+        <bug>54978</bug>: Make sure proper connection validation always happens,
+        regardless of config. (fhanik)
+      </fix>
+      <fix>
+        <bug>56318</bug>: Ability to trace statement creation in
+        <code>StatementFinalizer</code>. (fhanik)
+      </fix>
+      <fix>
+        <bug>56789</bug>: getPool() returns the actual pool, always. (fhanik)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <add>
+        <bug>56788</bug>: Display the full version in the list of installed
+        applications when installed via the Windows installer package. Patch
+        provided by Alexandre Garnier. (markt)
+      </add>
+      <add>
+        <bug>56829</bug>: Add the ability for users to define their own values
+        for <code>_RUNJAVA</code> and <code>_RUNJDB</code> environment
+        variables. Be more strict with executable filename on Windows
+        (s/java/java.exe/). Based on a patch by Neeme Praks. (markt/kkolinko)
+      </add>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.10 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>44312</bug>: Log an error if there is a conflict between Host and
+        Alias names. Improve host management methods in <code>Mapper</code>
+        to avoid occasionally removing a wrong host. Check that host management
+        operations are performed on the host and not on an alias. (kkolinko)
+      </fix>
+      <scode>
+        <bug>56611</bug>: Refactor code to remove inefficient calls to
+        <code>Method.isAnnotationPresent()</code>. Based on a patch by Jian Mou.
+        (markt/kkolinko)
+      </scode>
+      <fix>
+        Fix regression in
+        <code>StandardContext.removeApplicationListener()</code>, introduced by
+        the fix for bug <bug>56588</bug>. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56653</bug>: Fix concurrency issue with lists of contexts in
+        <code>Mapper</code> when stopping Contexts. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56657</bug>: When using parallel deployment, if the same session id
+        matches different versions of a web application, prefer the latest
+        version. Ensure that remapping selects the version that we expect.
+        (kkolinko)
+      </fix>
+      <fix>
+        Assert that mapping result object is empty before performing mapping
+        work in <code>Mapper</code>. (kkolinko)
+      </fix>
+      <scode>
+        Remove <code>context</code> and <code>wrapper</code> fields in
+        <code>Request</code> class and deprecate their setters. (kkolinko)
+      </scode>
+      <fix>
+        <bug>56658</bug>: Avoid delay between registrations of mappings for
+        context and for its servlets. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56665</bug>: Correct the generation of the effective web.xml when
+        elements contain an empty string as value. (violetagg)
+      </fix>
+      <fix>
+        Fix storeconfig exception routing issues, so that a major problem
+        should avoid configuration overwrite. (remm)
+      </fix>
+      <fix>
+        Add configuration fields for header names in SSLValve. (remm)
+      </fix>
+      <fix>
+        <bug>56666</bug>: When clearing the SSO cookie use the same values for
+        domain, path, httpOnly and secure as were used to set the SSO cookie.
+        (markt)
+      </fix>
+      <fix>
+        <bug>56677</bug>: Ensure that
+        <code>HttpServletRequest.getServletContext()</code> returns the correct
+        value during a cross-context dispatch. (markt)
+      </fix>
+      <fix>
+        <bug>56684</bug>: Ensure that Tomcat does not shut down if the socket
+        waiting for the shutdown command experiences a
+        <code>SocketTimeoutException</code>. (markt)
+      </fix>
+      <fix>
+        <bug>56693</bug>: Fix various issues in the static resource cache
+        implementation where the cache retained a stale entry after the
+        successful completion of an operation that always invalidates the cache
+        entry such as a delete operation.
+        (markt)
+      </fix>
+      <fix>
+        When the current PathInfo is modified as a result of dispatching a
+        request, ensure that a call to
+        <code>HttpServletRequest.getPathTranslated()</code> returns a value that
+        is based on the modified PathInfo. (markt)
+      </fix>
+      <fix>
+        <bug>56698</bug>: When persisting idle sessions, only persist newly idle
+        sessions. Patch provided by Felix Schumacher. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        <bug>56663</bug>: Fix edge cases demonstrated by ByteCounter relating
+        to data available, remaining and extra write events, mostly occurring
+        with non blocking Servlet 3.1. (remm)
+      </fix>
+      <fix>
+        Avoid possible NPE stopping endpoints that are not started (stop
+        shouldn't do anything in that case). (remm)
+      </fix>
+      <add>
+        <bug>56704</bug>: Add support for OpenSSL syntax for ciphers when
+        using JSSE SSL connectors. Submitted by Emmanuel Hugonnet. (remm)
+      </add>
+      <update>
+        Allow to configure <code>maxSwallowSize</code> attribute of an HTTP
+        connector via JMX. (kkolinko)
+      </update>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>56543</bug>: Update to the Eclipse JDT Compiler 4.4. (violetagg)
+      </fix>
+      <fix>
+        <bug>56652</bug>: Add support for method parameters that use arrays and
+        varargs to <code>ELProcessor.defineFunction()</code>.(markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <add>
+        Add support for the <code>permessage-deflate</code> extension. This is
+        currently limited to decompressing incoming messages on the server side.
+        It is expected that support will be extended to outgoing messages and to
+        the client side shortly. (markt)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Attempt to obfuscate session cookie values associated with other web
+        applications when viewing HTTP request headers with the Cookies example
+        from the examples web application. This reduces the opportunity to use
+        this example for malicious purposes should the advice to remove the
+        examples web application from security sensitive systems be ignored.
+        (markt)
+      </fix>
+      <fix>
+        <bug>56694</bug>: Remove references to <code>Manager</code> attribute
+        <code>checkInterval</code> from documentation and Javadoc since it no
+        longer exists. Based on a patch by Felix Schumacher. Also remove other
+        references to <code>checkInterval</code> that are no longer valid.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <update>
+        Update the API stability section of the release notes now that Tomcat 8
+        has had its first stable release. (markt)
+      </update>
+      <update>
+        Improve <code>build.xml</code> so that when Eclipse JDT Compiler is
+        updated, it will delete the old JAR from <code>build/lib</code>
+        directory. (kkolinko)
+      </update>
+      <scode>
+        Simplify implementation of "setproxy" target in <code>build.xml</code>.
+        (kkolinko)
+      </scode>
+      <update>
+        Update optional Checkstyle library to 5.7. (kkolinko)
+      </update>
+      <update>
+        <bug>56596</bug>: Update to Tomcat Native Library version 1.1.31 to
+        pick up the Windows binaries that are based on OpenSSL 1.0.1h. (markt)
+      </update>
+      <fix>
+        <bug>56685</bug>: Add quotes necessary for <code>daemon.sh</code> to
+        work correctly on Solaris. Based on a suggesiton by lfuka. (markt)
+      </fix>
+      <update>
+        Update package renamed Apache Commons Pool2 to r1609323 to pick various
+        bug fixes. (markt)
+      </update>
+      <update>
+        Update package renamed Apache Commons DBCP2 to r1609329 to pick up a
+        minor bug fix. (markt)
+      </update>
+      <update>
+        Update package renamed Apache Commons FileUpload to r1596086 to pick
+        various bug fixes. (markt)
+      </update>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.9 (markt)" rtext="2014-06-24">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>55282</bug>: Ensure that one and the same application listener is
+        added only once when starting the web application. (violetagg)
+      </fix>
+      <fix>
+        <bug>55975</bug>: Apply consistent escaping for double quote and
+        backslash characters when escaping cookie values. (markt)
+      </fix>
+      <scode>
+        <bug>56387</bug>: Improve the code that handles an attempt to load a
+        class after a web application has been stopped. Use common code to handle
+        this case regardless of the access path and don't throw an exception
+        purely to log a stack trace. (markt)
+      </scode>
+      <scode>
+        <bug>56399</bug>: Improve implementation of CoyoteAdapter.checkRecycled()
+        to do not use an exception for flow control. (kkolinko)
+      </scode>
+      <add>
+        <bug>56461</bug>: New <code>failCtxIfServletStartFails</code> attribute
+        on Context and Host configuration to force the context startup to fail
+        if a load-on-startup servlet fails its startup. (slaurent)
+      </add>
+      <add>
+        <bug>56526</bug>: Improved the <code>StuckThreadDetectionValve</code> to
+        optionally interrupt stuck threads to attempt to unblock them.
+        (slaurent)
+      </add>
+      <fix>
+        <bug>56545</bug>: Pre-load two additional classes, the loading of which
+        may otherwise be triggered by a web application which in turn would
+        trigger an exception when running under a security manager. (markt)
+      </fix>
+      <update>
+        <bug>56546</bug>: Reduce logging level for stack traces of stuck web
+        application threads printed by WebappClassLoader.clearReferencesThreads()
+        from error to info. (kkolinko)
+      </update>
+      <scode>
+        Refactor and simplify common code in object factories in
+        <code>org.apache.catalina.naming</code> package, found thanks to Simian
+        (Similarity Analyser) tool. Improve handling of Throwable.
+        (markt/kkolinko)
+      </scode>
+      <fix>
+        Relax cookie naming restrictions. Cookie attribute names used in the
+        <code>Set-Cookie</code> header may be used unambiguously as cookie
+        names. The restriction that prevented such usage has been removed.
+        (jboynes/markt)
+      </fix>
+      <fix>
+        Further relax cookie naming restrictions. Version 0 (a.k.a Netscape
+        format) cookies may now use names that start with the <code>$</code>
+        character. (jboynes/markt)
+      </fix>
+      <fix>
+        Restrict cookie naming so that the <code>=</code> character is no longer
+        permitted in a version 0 (a.k.a. Netscape format) cookie name. While
+        Tomcat allowed this, browsers always truncated the name at the
+        <code>=</code> character leading to a mis-match between the cookie the
+        server set and the cookie returned by the browser. (jboynes/markt)
+      </fix>
+      <add>
+        Add a simple <code>ServiceLoader</code> based discovery mechanism to the
+        JULI <code>LogFactory</code> to make it easier to use JULI and Tomcat
+        components that depend on JULI (such as Jasper) independently from
+        Tomcat. Patch provided by Greg Wilkins. (markt)
+      </add>
+      <fix>
+        <bug>56578</bug>: Correct regression in the fix for <bug>56339</bug>
+        that prevented sessions from expiring when using clustering. (markt)
+      </fix>
+      <fix>
+        <bug>56588</bug>: Remove code previously added to enforce the
+        requirements of section 4.4 of the Servlet 3.1 specification. The code
+        is no longer required now that Jasper initialization has been refactored
+        and TLD defined listeners are added via a different code path that
+        already enforces the specification requirements. (markt)
+      </fix>
+      <fix>
+        <bug>56600</bug>: In WebdavServlet: Do not waste time generating
+        response for broken PROPFIND request. (kkolinko)
+      </fix>
+      <fix>
+        Provide a better error message when asynchronous operations are not
+        supported by a filter or servlet. Patch provided by Romain Manni-Bucau.
+        (violetagg)
+      </fix>
+      <fix>
+        <bug>56606</bug>: User entries in <code>tomcat-users.xml</code> file
+        are recommended to use "username" attribute rather than legacy "name"
+        attribute. Fix inconsistencies in Windows installer, examples. Update
+        digester rules and documentation for <code>MemoryRealm</code>.
+        (markt/kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        <bug>56518</bug>: When using NIO, do not attempt to write to the socket
+        if the thread is marked interrupted as this will lead to a connection
+        limit leak. This fix was based on analysis of the issue by hanyong.
+        (markt)
+      </fix>
+      <fix>
+        <bug>56521</bug>: Re-use the asynchronous write buffer between writes to
+        reduce allocation and GC overhead. Based on a patch by leonzhx. Also
+        make the buffer size configurable and remove copying of data within
+        buffer when the buffer is only partially written on a subsequent write.
+        (markt)
+      </fix>
+      <fix>
+        Ensure that a request without a body is correctly handled during Comet
+        processing. This fixes the Comet chat example. (markt)
+      </fix>
+      <fix>
+        Fix input concurrency issue in NIO2 upgrade. (remm)
+      </fix>
+      <fix>
+        Correct a copy/paste error and return a 500 response rather than a 400
+        response when an internal server error occurs on early stages of
+        request processing. (markt)
+      </fix>
+      <scode>
+        <bug>56582</bug>: Use switch(actionCode) in processors instead of a
+        chain of "elseif"s. (kkolinko)
+      </scode>
+      <fix>
+        <bug>56582#c1</bug>: Implement DISPATCH_EXECUTE action for AJP
+        connectors. (kkolinko)
+      </fix>
+      <fix>
+        If request contains an unrecognized Expect header, respond with error
+        417 (Expectation Failed), according to RFC2616 chapter 14.20. (markt)
+      </fix>
+      <fix>
+        When an error occurs after the response has been committed close the
+        connection immediately rather than attempting to finish the response to
+        make it easier for the client to differentiate between a complete
+        response and one that failed part way though. (markt)
+      </fix>
+      <scode>
+        Remove the beta tag from the NIO2 connectors. (remm)
+      </scode>
+      <fix>
+        <bug>56620</bug>: Avoid bogus access log entries when pausing the NIO
+        HTTP connector and ensure that access log entries generated by error
+        conditions use the correct request start time. (markt)
+      </fix>
+      <fix>
+        Improve configuration of cache sizes in the endpoint. (markt)
+      </fix>
+      <add>
+        Add a new limit, defaulting to 2MB, for the amount of data Tomcat will
+        swallow for an aborted upload. The limit is configurable by
+        <code>maxSwallowSize</code> attribute of an HTTP connector. (markt)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>56334#c15</bug>: Fix a regression in EL parsing when quoted string
+        follows a whitespace. (kkolinko/markt)
+      </fix>
+      <update>
+        <bug>56543</bug>: Update to the Eclipse JDT Compiler 4.4RC4 to pick up
+        some fixes for Java 8 support. (markt/kkolinko)
+      </update>
+      <fix>
+        <bug>56561</bug>: Avoid <code>NoSuchElementException</code> while
+        handling attributes with empty string value. (violetagg)
+      </fix>
+      <scode>
+        Do not configure a <code>JspFactory</code> in the
+        <code>JasperInitializer</code> if one has already been set as might be
+        the case in some embedding scenarios. (markt)
+      </scode>
+      <add>
+        Add a simple implementation of <code>InstanceManager</code> and have
+        Jasper use it if no other <code>InstanceManager</code> is provided. This
+        makes it easier to use Jasper independently from Tomcat. Patch provided
+        by Greg Wilkins. (markt)
+      </add>
+      <fix>
+        <bug>56568</bug>: Allow any HTTP method when a JSP is being used as an
+        error page. (markt)
+      </fix>
+      <update>
+        <bug>56581</bug>: If an error on a JSP page occurs when response has
+        already been committed, do not clear the buffer of JspWriter, but flush
+        it. It will make more clear where the error occurred. (kkolinko)
+      </update>
+      <fix>
+        <bug>56612</bug>: Correctly parse two consecutive escaped single quotes
+        when used in UEL expression in a JSP. (markt)
+      </fix>
+      <update>
+        Move code that parses EL expressions within JSP template text from
+        <code>Parser</code> to <code>JspReader</code> class for better
+        performance. (kkolinko)
+      </update>
+      <fix>
+        <bug>56636</bug>: Correctly identify the required method when specified
+        via <code>ELProcessor.defineFunction(String,String,String,String)</code>
+        when using Expression Language. (markt)
+      </fix>
+      <fix>
+        <bug>56638</bug>: When using
+        <code>ELProcessor.defineFunction(String,String,String,String)</code> and
+        no function name is specified, use the method name as the function name
+        as required by the specification. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <scode>
+        <bug>56446</bug>: Clearer handling of exceptions when calling a method
+        on a POJO based WebSocket endpoint. Based on a suggestion by Eugene
+        Chung. (markt)
+      </scode>
+      <fix>
+        When a WebSocket client attempts to write to a closed connection, handle
+        the resulting <code>IllegalStateException</code> in a manner consistent
+        with the handling of an <code>IOException</code>. (markt)
+      </fix>
+      <fix>
+        Add more varied endpoints for echo testing. (remm)
+      </fix>
+      <fix>
+        <bug>56577</bug>: Improve the executor configuration used for the
+        callbacks associated with asynchronous writes. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Set the path for cookies created by the examples web application so they
+        only returned to the examples application. This reduces the opportunity
+        for using such cookies for malicious purposes should the advice to
+        remove the examples web application from security sensitive systems be
+        ignored. (markt/kkolinko)
+      </fix>
+      <fix>
+        Attempt to obfuscate session cookie values associated with other web
+        applications when viewing HTTP request headers with the Request Header
+        example from the examples web application. This reduces the opportunity
+        to use this example for malicious purposes should the advice to remove
+        the examples web application from security sensitive systems be ignored.
+        (markt)
+      </fix>
+      <add>
+        Add options for all of the WebSocket echo endpoints to the WebSocket
+        echo example in the examples web application. (markt)
+      </add>
+      <fix>
+        Ensure that the asynchronous WebSocket echo endpoint in the examples
+        web application always waits for the previous message to complete before
+        it sends the next. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <update>
+        Update package renamed Apache Commons DBCP2 to r1596858. (markt)
+      </update>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.8 (markt)" rtext="beta, 2014-05-21">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>56536</bug>: Ensure that
+        <code>HttpSessionBindingListener.valueUnbound()</code> uses the correct
+        class loader when the <code>SingleSignOn</code> valve is used. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+       <fix>
+         <bug>56529</bug>: Avoid <code>NoSuchElementException</code> while handling
+         attributes with empty string value in custom tags. Patch provided by
+         Hariprasad Manchi. (violetagg)
+       </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.7 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>56523</bug>: When using SPNEGO authentication, log the exceptions
+        associated with failed user logins at debug level rather than error
+        level. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <add>
+        <bug>56399</bug>: Assert that both Coyote and Catalina request objects
+        have been properly recycled. (kkolinko)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>56522</bug>: When setting a value for a
+        <code>ValueExpression</code>, ensure that the expected coercions take
+        place such as a <code>null</code> string being coerced to an empty
+        string. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        Copy missing resources file from Apache Commons DBCP 2 to packaged
+        renamed copy of DBCP 2. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.6 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        Fix extension validation which was broken by refactoring for new
+        resources implementation. (markt)
+      </fix>
+      <fix>
+        Fix custom UTF-8 decoder so that a byte of value 0xC1 is always rejected
+        immediately as it is never valid in a UTF-8 byte sequence. Update UTF-8
+        decoder tests to account for UTF-8 decoding improvements in Java 8.
+        The custom UTF-8 decoder is still required due to bugs in the UTF-8
+        decoder provided by Java. Java 8&apos;s decoder is better than Java
+        7&apos;s but it is still buggy. (markt)
+      </fix>
+      <fix>
+        <bug>56027</bug>: Add more options for managing FIPS mode in the
+        AprLifecycleListener. (schultz/kkolinko)
+      </fix>
+      <fix>
+        <bug>56320</bug>: Fix a file descriptor leak in the default servlet when
+        sendfile is used. (markt)
+      </fix>
+      <fix>
+        <bug>56321</bug>: When a WAR is modified, undeploy the web application
+        before deleting any expanded directory as the undeploy process may
+        refer to classes that need to be loaded from the expanded directory. If
+        the expanded directory is deleted first, any attempt to load a new class
+        during undeploy will fail. (markt)
+      </fix>
+      <fix>
+        <bug>56327</bug>: Enable AJP as well as HTTP connectors to be created
+        via JMX. Patch by kiran. (markt)
+      </fix>
+      <fix>
+        <bug>56339</bug>: Avoid an infinite loop if an application calls
+        <code>session.invalidate()</code> from the session destroyed event for
+        that session. (markt)
+      </fix>
+      <scode>
+        <bug>56365</bug>: Simplify file name pattern matching code in
+        <code>StandardJarScanner</code>. Improve documentation. (kkolinko)
+      </scode>
+      <fix>
+        Ensure that the static resource cache is able to detect when a cache
+        entry is invalidated by being overridden by a new resource in a
+        different <code>WebResourceSet</code>. (markt)
+      </fix>
+      <fix>
+        <bug>56369</bug>: Ensure that removing an MBean notification listener
+        reverts all the operations performed when adding an MBean notification
+        listener. (markt)
+      </fix>
+      <scode>
+        Improve implementation of <code>Lifecycle</code> for
+        <code>WebappClassLoader</code>. State is now correctly reported rather
+        than always reporting as <code>NEW</code>. (markt)
+      </scode>
+      <add>
+        <bug>56382</bug>: Information about finished deployment and its execution
+        time is added to the log files. Patch is provided by Danila Galimov.
+        (violetagg)
+      </add>
+      <add>
+        <bug>56383</bug>: Properties for disabling server information and error
+        report are added to the <code>org.apache.catalina.valves.ErrorReportValve</code>.
+        Based on the patch provided by Nick Bunn. (violetagg/kkolinko)
+      </add>
+      <fix>
+        <bug>56390</bug>: Fix JAR locking issue with JARs containing TLDs and
+        the TLD cache that prevented the undeployment of web applications when
+        the WAR was deleted. (markt)
+      </fix>
+      <fix>
+        Fix CVE-2014-0119:
+        Only create XML parsing objects if required and fix associated potential
+        memory leak in the default Servlet.
+        Extend XML factory, parser etc. memory leak protection to cover some
+        additional locations where, theoretically, a memory leak could occur.
+        (markt)
+      </fix>
+      <fix>
+        Modify generic exception handling so that
+        <code>StackOverflowError</code> is not treated as a fatal error and can
+        handled and/or logged as required. (markt)
+      </fix>
+      <fix>
+        <bug>56409</bug>: Avoid <code>StackOverflowError</code> on non-Windows
+        systems if a file named <code>\</code> is encountered when scanning for
+        TLDs. (markt)
+      </fix>
+      <add>
+        <bug>56430</bug>: Extend checks for suspicious URL patterns to include
+        patterns of the form <code>*.a.b</code> which are not valid patterns for
+        extension mappings. (markt)
+      </add>
+      <fix>
+        <bug>56441</bug>: Raise the visibility of exceptions thrown when a
+        problem is encountered calling a getter or setter on a component
+        attribute. The logging level is raised from debug to warning. (markt)
+      </fix>
+      <add>
+        <bug>56463</bug>: Property for disabling server information is added to
+        the <code>DefaultServlet</code>. Server information is presented in the
+        response sent to the client when directory listings is enabled.
+        (violetagg)
+      </add>
+      <fix>
+        <bug>56472</bug>: Allow NamingContextListener to clean up on stop if its
+        start failed. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56481</bug>: Work around case insensitivity issue in
+        <code>URLClassLoader</code> exposed by some recent refactoring. (markt)
+      </fix>
+      <add>
+        <bug>56492</bug>: Avoid eclipse debugger pausing on uncaught exceptions
+        when tomcat renews its threads. (slaurent)
+      </add>
+      <add>
+        Add the <code>org.apache.naming</code> package to the packages requiring
+        code to have the <code>defineClassInPackage</code> permission when
+        running under a security manager. (markt)
+      </add>
+      <fix>
+        Make the naming context tokens for containers more robust by using a
+        separate object. Require RuntimePermission when introducing a new token.
+        (markt/kkolinko)
+      </fix>
+      <fix>
+        <bug>56501</bug>: <code>HttpServletRequest.getContextPath()</code>
+        should return the undecoded context path used by the user agent. (markt)
+      </fix>
+      <fix>
+        Minor fixes to <code>ThreadLocalLeakPreventionListener</code>. Do not
+        trigger threads renewal for failed contexts. Do not ignore
+        <code>threadRenewalDelay</code> setting. Improve documentation. (kkolinko)
+      </fix>
+      <fix>
+        Correct regression introduced in <rev>1239520</rev> that broke loading
+        of users from <code>tomcat-users.xml</code> when using the
+        <code>JAASMemoryLoginModule</code>. (markt)
+      </fix>
+      <fix>
+        Correct regression introduced in <rev>797162</rev> that broke
+        authentication of users when using the
+        <code>JAASMemoryLoginModule</code>. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        More cleanup of NIO2 endpoint shutdown. (remm)
+      </fix>
+      <fix>
+        <bug>56336</bug>: AJP output corruption and errors. (remm)
+      </fix>
+      <fix>
+        Handle various cases of incomplete writes in NIO2. (remm)
+      </fix>
+      <scode>
+        Code cleanups and i18n in NIO2. (remm)
+      </scode>
+      <fix>
+        Fix extra onDataAvailable calls in the NIO2 connector. (remm)
+      </fix>
+      <fix>
+        Fix gather writes in NIO2 SSL. (remm)
+      </fix>
+      <scode>
+        Upgrade the NIO2 connectors to beta, but still not ready for production. (remm)
+      </scode>
+      <scode>
+        Fix code duplication between NIO and NIO2. (remm)
+      </scode>
+      <fix>
+        <bug>56348</bug>: Fix slow asynchronous read when read was performed on
+        a non-container thread. (markt)
+      </fix>
+      <fix>
+        <bug>56416</bug>: Correct documentation for default value of socket
+        linger for the AJP and HTTP connectors. (markt)
+      </fix>
+      <fix>
+        Fix possible corruption if doing keepalive after a comet request. (remm)
+      </fix>
+      <fix>
+        <bug>56518</bug>: Fix connection limit latch leak when a non-container
+        thread is interrupted during asynchronous processing. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>56334</bug>: Fix a regression in the handling of back-slash
+        escaping introduced by the fix for <bug>55735</bug>. (markt/kkolinko)
+      </fix>
+      <fix>
+        <bug>56425</bug>: Improve method matching for EL expressions. When
+        looking for matching methods, an exact match between parameter types is
+        preferred followed by an assignable match followed by a coercible match.
+        (markt)
+      </fix>
+      <fix>
+        Correct the handling of back-slash escaping in the EL parser and no
+        longer require that <code>\$</code> or <code>\#</code> must be followed
+        by <code>{</code> in order for the back-slash escaping to take effect.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <scode>
+        Remove the implementation of
+        <code>org.apache.catalina.LifecycleListener</code> from
+        <code>org.apache.catalina.ha.tcp.SimpleTcpCluster</code>.
+        <code>SimpleTcpCluster</code> does not work as
+        <code>LifecycleListener</code>, it works as nested components of Host or
+        Engine. (kfujino)
+      </scode>
+      <fix>
+        Remove cluster and replicationValve from cluster manager template. These
+        instance are not necessary to template. (kfujino)
+      </fix>
+      <fix>
+        Add support for cross context session replication to
+        <code>org.apache.catalina.ha.session.BackupManager</code>. (kfujino)
+      </fix>
+      <fix>
+        Remove the unnecessary cross context check. It does not matter whether
+        the context that is referenced by other context is set to
+        <code>crossContext</code>=true. The context that refers to the different
+        context must be set to <code>crossContext</code>=true. (kfujino)
+      </fix>
+      <scode>
+        Move to <code>org.apache.catalina.ha.session.ClusterManagerBase</code>
+        common logics of
+        <code>org.apache.catalina.ha.session.BackupManager</code> and
+        <code>org.apache.catalina.ha.session.DeltaManager</code>. (kfujino)
+      </scode>
+      <scode>
+        Simplify the code of <code>o.a.c.ha.tcp.SimpleTcpCluster</code>. In
+        order to add or remove cluster valve to Container, use pipeline instead
+        of <code>IntrospectionUtils</code>. (kfujino)
+      </scode>
+      <fix>
+        There is no need to set cluster instance when
+        <code>SimpleTcpCluster.unregisterClusterValve</code> is called.
+        Set null than cluster instance for cleanup. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        <bug>56343</bug>: Avoid a NPE if Tomcat&apos;s Java WebSocket 1.0
+        implementation is used with the Java WebSocket 1.0 API JAR from the
+        reference implementation. (markt)
+      </fix>
+      <fix>
+        Increase the default maximum size of the executor used by the WebSocket
+        implementation for call backs associated with asynchronous writes from
+        10 to 200. (markt)
+      </fix>
+      <add>
+        Add a warning if the thread group created for WebSocket asynchronous
+        write call backs can not be destroyed when the web application is
+        stopped. (markt)
+      </add>
+      <fix>
+        Ensure that threads created to support WebSocket clients are stopped
+        when no longer required. This will happen automatically for WebSocket
+        client connections initiated by web applications but stand alone clients
+        must call <code>WsWebSocketContainer.destroy()</code>. (markt)
+      </fix>
+      <fix>
+        <bug>56449</bug>: When creating a new session, add the message handlers
+        to the session before calling <code>Endpoint.onOpen()</code> so the
+        message handlers are in place should the <code>onOpen()</code> method
+        trigger the sending of any messages. (markt)
+      </fix>
+      <fix>
+        <bug>56458</bug>: Report WebSocket sessions that are created over secure
+        connections as secure rather than as not secure. (markt)
+      </fix>
+      <fix>
+        Stop threads used for secure WebSocket client connections when they are
+        no longer required and give them better names for easier debugging while
+        they are running. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        Add Support for <code>copyXML</code> attribute of Host to Host Manager.
+        (kfujino)
+      </fix>
+      <fix>
+        Ensure that "name" request parameter is used as a application base of
+        host if "webapps" request parameter is not set when adding host in
+        HostManager Application. (kfujino)
+      </fix>
+      <fix>
+        Correct documentation on Windows service options, aligning it with
+        Apache Commons Daemon documentation. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56418</bug>: Ensure that the Manager web application does not
+        report success for a web application deployment that fails. (slaurent)
+      </fix>
+      <update>
+        Improve valves documentation. Split valves into groups. (kkolinko)
+      </update>
+      <fix>
+        <bug>56513</bug>: Make the documentation crystal clear that using
+        sendfile will disable any compression that Tomcat may otherwise have
+        applied to the response. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <scode>
+        Review source code and take advantage of Java 7&apos;s
+        try-with-resources syntax where possible. (markt)
+      </scode>
+      <fix>
+        Align DisplayName of Tomcat installed by <code>service.bat</code> with
+        one installed by the *.exe installer. Print a warning in case if neither
+        server nor client jvm is found by <code>service.bat</code>. (kkolinko)
+      </fix>
+      <update>
+        <bug>56363</bug>: Update to version 1.1.30 of Tomcat Native library.
+        (schultz)
+      </update>
+      <update>
+        Update package renamed Apache Commons BCEL to r1593495 to pick up some
+        additional changes for Java 7 support and some code clean up. (markt)
+      </update>
+      <update>
+        Update package renamed Apache Commons FileUpload to r1569132 to pick up
+        some small improvements (e.g. better <code>null</code> protection) and
+        some code clean up. (markt)
+      </update>
+      <update>
+        Update package renamed Apache Commons Codec to r1586336 to pick up some
+        Javadoc fixes and some code clean up. (markt)
+      </update>
+      <scode>
+        Switch to including Apache Commons DBCP via a package renamed svn copy
+        rather than building from a source release for consistency with other
+        Commons packages and to allow faster releases to fix DBCP related
+        issues. (markt)
+      </scode>
+      <update>
+        Update package renamed Apache Commons Pool2 and DBCP2 to r1593563 to
+        pick various bug fixes. (markt)
+      </update>
+      <add>
+        In tests: allow to configure directory where JUnit reports and access
+        log are written to. (kkolinko)
+      </add>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.5 (markt)" rtext="beta, 2014-03-27">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        Rework the fix for <bug>56190</bug> as the previous fix did not recycle
+        the request in all cases leading to mis-routing of requests. (markt)
+      </fix>
+      <fix>
+        Allow web applications to package tomcat-jdbc.jar and their JDBC driver
+        of choice in the web application. (markt)
+      </fix>
+      <fix>
+        <bug>56293</bug>: Cache resources loaded by the class loader from
+        <code>/META-INF/services/</code> for better performance for repeated
+        look ups. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Fix possibly incomplete final flush with NIO2 when using non blocking
+        mode. (remm)
+      </fix>
+      <fix>
+        Cleanup NIO2 endpoint shutdown. (remm)
+      </fix>
+      <fix>
+        Fix rare race condition notifying onWritePossible in the NIO2
+        HTTP/1.1 connector. (remm)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        <bug>54475</bug>: Add Java 8 support to SMAP generation for JSPs. Patch
+        by Robbie Gibson. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        <bug>56273</bug>: If the Manager web application does not perform an
+        operation because the web application is already being serviced, report
+        an error rather than reporting success. (markt)
+      </fix>
+      <fix>
+        <bug>56304</bug>: Add a note to the documentation about not using
+        WebSocket with BIO HTTP in production. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.4 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        Restore the ability to use the <code>addURL()</code> method of the
+        web application class loader to add external resources to the web
+        application. (markt)
+      </fix>
+      <fix>
+        Improve the robustness of web application undeployment based on some
+        code analysis triggered by the report for <bug>54315</bug>. (markt)
+      </fix>
+      <fix>
+        <bug>56125</bug>: Correctly construct the URL for a resource that
+        represents the root of a JAR file. (markt)
+      </fix>
+      <fix>
+        Generate a valid root element for the effective web.xml for a web
+        application for all supported versions of web.xml. (markt)
+      </fix>
+      <add>
+        Make it easier for applications embedding and/or extending Tomcat to
+        modify the <code>javaseClassLoader</code> attribute of the
+        <code>WebappClassLoader</code>. (markt)
+      </add>
+      <fix>
+        Add missing support for <code>&lt;deny-uncovered-http-methods&gt;</code>
+        element when merging web.xml files. (markt)
+      </fix>
+      <fix>
+        Improve merging process for web.xml files to take account of the
+        elements and attributes supported by the Servlet version of the merged
+        file. (markt)
+      </fix>
+      <fix>
+        Avoid <code>NullPointerException</code> in resource cache when making an
+        invalid request for a resource outside of the web application. (markt)
+      </fix>
+      <fix>
+        Remove an unnecessary null check identified by FindBugs. (markt)
+      </fix>
+      <add>
+        In WebappClassLoader, when reporting threads that are still running
+        while web application is being stopped, print their stack traces to
+        the log. (kkolinko)
+      </add>
+      <fix>
+        <bug>56190</bug>: The response should be closed (i.e. no further output
+        is permitted) when a call to <code>AsyncContext.complete()</code> takes
+        effect. (markt)
+      </fix>
+      <fix>
+        <bug>56236</bug>: Enable Tomcat to work with alternative Servlet and
+        JSP API JARs that package the XML schemas in such as way as to require
+        a dependency on the JSP API before enabling validation for web.xml.
+        Tomcat has no such dependency. (markt)
+      </fix>
+      <fix>
+        <bug>56244</bug>: Fix MBeans descriptor for WebappClassLoader MBean.
+        (kkolinko)
+      </fix>
+      <add>
+        Add a work around for validating XML documents (often TLDs) that use
+        just the file name to refer to refer to the JavaEE schema on which they
+        are based. (markt)
+      </add>
+      <add>
+        Add methods of get the idle time from last client access time to
+        <code>org.apache.catalina.Session</code>. (kfujino)
+      </add>
+      <fix>
+        <bug>56246</bug>: Fix NullPointerException in MemoryRealm when
+        authenticating an unknown user. (markt)
+      </fix>
+      <fix>
+        <bug>56248</bug>: Allow the deployer to update an existing WAR file
+        without undeploying the existing application if the update flag is set.
+        This allows any existing custom context.xml for the application to be
+        retained. To update an application and remove any existing context.xml
+        simply undeploy the old version of the application before deploying the
+        new version. (markt)
+      </fix>
+      <fix>
+        <bug>56253</bug>: When listing resources that are provided by a JAR, fix
+        possible <code>StringIndexOutOfBoundsException</code>s. Add some unit
+        tests for this and similar scenarios and fix the additional issues those
+        unit tests identified. Based on a patch by Larry Isaacs. (markt)
+      </fix>
+      <fix>
+        Fix CVE-2014-0096:
+        Redefine the <code>globalXsltFile</code> initialisation parameter of the
+        DefaultServlet as relative to CATALINA_BASE/conf or CATALINA_HOME/conf.
+        Prevent user supplied XSLTs used by the DefaultServlet from defining
+        external entities. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        In some circumstances asynchronous requests could time out too soon.
+        (markt)
+      </fix>
+      <fix>
+        <bug>56172</bug>: Avoid possible request corruption when using the AJP
+        NIO connector and a request is sent using more than one AJP message.
+        Patch provided by Amund Elstad. (markt)
+      </fix>
+      <add>
+        Add experimental NIO2 connector. Based on code developed by
+        Nabil Benothman. (remm)
+      </add>
+      <fix>
+        Fix CVE-2014-0075:
+        Improve processing of chuck size from chunked headers. Avoid overflow
+        and use a bit shift instead of a multiplication as it is marginally
+        faster. (markt/kkolinko)
+      </fix>
+      <fix>
+        Fix CVE-2014-0095:
+        Correct regression introduced in 8.0.0-RC2 as part of the Servlet 3.1
+        non-blocking IO support that broke handling of requests with an explicit
+        content length of zero. (markt/kkolinko)
+      </fix>
+      <fix>
+        Fix CVE-2014-0099:
+        Fix possible overflow when parsing long values from a byte array.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        Change the default compiler source and compiler target versions to 1.7
+        since Tomcat 8 requires a minimum of Java 7. (markt)
+      </fix>
+      <fix>
+        <bug>56179</bug>: Fix parsing of EL expressions that contain unnecessary
+        parentheses. (markt)
+      </fix>
+      <fix>
+        <bug>56177</bug>: Handle dependency tracking for TLDs when using JspC
+        with a tag library JAR that is located outside of the web application.
+        (markt)
+      </fix>
+      <fix>
+        Remove an unnecessary null check identified by FindBugs. (markt)
+      </fix>
+      <fix>
+        <bug>56199</bug>: Restore validateXml option for JspC which determines
+        if web.xml will be parsed with a validating parser. (markt)
+      </fix>
+      <fix>
+        <bug>56223</bug>: Throw an <code>IllegalStateException</code> if a call
+        is made to <code>ServletContext.setInitParameter()</code> after the
+        ServletContext has been initialized. (markt)
+      </fix>
+      <fix>
+        <bug>56265</bug>: Do not escape values of dynamic tag attributes
+        containing EL expressions. (kkolinko)
+      </fix>
+      <fix>
+        Make the default compiler source and target versions for JSPs Java 7
+        since Tomcat 8 requires Java 7 as a minimum. (markt)
+      </fix>
+      <update>
+        <bug>56283</bug>: Update to the Eclipse JDT Compiler P20140317-1600
+        which adds support for Java 8 syntax to JSPs. Add support for value
+        "1.8" for the <code>compilerSourceVM</code> and
+        <code>compilerTargetVM</code> options. (markt)
+      </update>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        Avoid a possible deadlock when one thread is shutting down a connection
+        while another thread is trying to write to it. (markt)
+      </fix>
+      <fix>
+        Avoid NPE when flushing batched messages. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Web Applications">
+    <changelog>
+      <add>
+        <bug>56093</bug>: Add the SSL Valve to the documentation web
+        application. (markt)
+      </add>
+      <fix>
+        <bug>56217</bug>: Improve readability by using left alignment for the
+        table cell containing the request information on the Manager application
+        status page. (markt)
+      </fix>
+      <fix>
+        Fixed <code>java.lang.NegativeArraySizeException</code> when using
+        "Expire sessions" command in the manager web application on a
+        context where the session timeout is disabled. (kfujino)
+      </fix>
+      <fix>
+         Add support for <code>LAST_ACCESS_AT_START</code> system property to
+         Manager web application. (kfujino)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        <bug>56115</bug>: Expose the <code>httpusecaches</code> property of
+        Ant&apos;s <code>get</code> task as some users may need to change the
+        default. Based on a suggestion by Anthony. (markt)
+      </fix>
+      <fix>
+        <bug>56143</bug>: Improve <code>service.bat</code> so that it can be
+        launched from a non-UAC console. This includes using a single call to
+        <code>tomcat8.exe</code> to install the Windows service rather than
+        three calls, and using command line arguments instead of environment
+        variables to pass the settings. (markt/kkolinko)
+      </fix>
+      <scode>
+        Simplify Windows *.bat files: remove %OS% checks, as current java does
+        not run on ancient non-NT operating systems. (kkolinko)
+      </scode>
+      <fix>
+        Align options between <code>service.bat</code> and <code>exe</code>
+        Windows installer. For <code>service.bat</code> the changes are in
+        --Classpath, --DisplayName, --StartPath, --StopPath. For
+        <code>exe</code> installer the changes are in --JvmMs, --JvmMx options,
+        which are now 128 Mb and 256 Mb respectively instead of being empty.
+        Explicitly specify --LogPath path when uninstalling Windows service,
+        avoiding default value for that option. (kkolinko)
+      </fix>
+      <fix>
+        <bug>56137</bug>: Explicitly use NIO connector in SSL example in
+        server.xml so it doesn't break if APR is enabled. (markt)
+      </fix>
+      <fix>
+        <bug>56139</bug>: Avoid a web application class loader leak in some unit
+        tests when running on Windows. (markt)
+      </fix>
+      <fix>
+        Correct build script to avoid building JARs with empty packages. (markt)
+      </fix>
+      <add>
+        Allow to limit JUnit test run to a number of selected test case
+        methods. (kkolinko)
+      </add>
+      <update>
+        Update Commons Pool 2 to 2.2. (markt)
+      </update>
+      <update>
+        Update Commons DBCP 2 to the 2.0 release. (markt)
+      </update>
+      <fix>
+        <bug>56189</bug>: Remove used file cpappend.bat from the distribution.
+        (markt)
+      </fix>
+      <fix>
+        <bug>56204</bug>: Remove unnecessary dependency between tasks in the
+        build script. (markt)
+      </fix>
+      <fix>
+         Add definition of <code>org.apache.catalina.ant.FindLeaksTask</code>.
+         (kfujino)
+      </fix>
+      <fix>
+         Implement <code>org.apache.catalina.ant.VminfoTask</code>,
+         <code>org.apache.catalina.ant.ThreaddumpTask</code> and
+         <code>org.apache.catalina.ant.SslConnectorCiphersTask</code>. (kfujino)
+      </fix>
+      <add>
+         Add the option to the Apache Ant tasks to ignore the constraint of the
+         first line of the response message that must be "OK -"
+         (<code>ignoreResponseConstraint</code> in <code>AbstractCatalinaTask</code>).
+         Default is false. (kfujino)
+      </add>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.3 (markt)" rtext="beta, 2014-02-11">
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        Fix build of Apache Commons DBCP2 classes. (kkolinko)
+      </fix>
+      <update>
+        Update Commons DBCP 2 to snapshot 170 dated 07 Feb 2014. This enables
+        DBCP to work with a SecurityManager such that only DBCP needs to be
+        granted the necessary permissions to communicate with the database.
+        (markt)
+      </update>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.2 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        <bug>56082</bug>: Fix a concurrency bug in JULI&apos;s LogManager
+        implementation. (markt)
+      </fix>
+      <fix>
+        <bug>56085</bug>: <code>ServletContext.getRealPath(String)</code> should
+        return <code>null</code> for invalid input rather than throwing an
+        <code>IllegalArgumentException</code>. (markt)
+      </fix>
+      <fix>
+        Fix WebDAV support that was broken by the refactoring for the new
+        resources implementation. (markt)
+      </fix>
+      <scode>
+        Simplify Catalina.initDirs(). (kkolinko)
+      </scode>
+      <fix>
+        <bug>56096</bug>: When the attribute <code>rmiBindAddress</code> of the
+        JMX Remote Lifecycle Listener is specified it's value will be used when
+        constructing the address of a JMX API connector server. Patch is
+        provided by Jim Talbut. (violetagg)
+      </fix>
+      <fix>
+        When environment entry with one and the same name is defined in the web
+        deployment descriptor and with annotation then the one specified in the
+        web deployment descriptor is with priority. (violetagg)
+      </fix>
+      <fix>
+        Fix passing the value of false for <code>xmlBlockExternal</code> option
+        of Context to Jasper, as the default was changed in 8.0.1. (kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Enable non-blocking reads to take place on non-container threads.
+        (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <scode>
+        Simplify the code of
+        <code>o.a.c.ha.tcp.SimpleTcpCluster.createManager(String)</code>.
+        Remove unnecessary class cast. (kfujino)
+      </scode>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <fix>
+        In Manager web application improve handling of file upload errors.
+        Display a message instead of error 500 page. Simplify. (kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <fix>
+        <bug>56104</bug>: Correct the version number on the welcome page of the
+        Windows installer. (markt)
+      </fix>
+      <update>
+        Update Commons DBCP 2 to snapshot 168 dated 05 Feb 2014. (markt)
+      </update>
+      <fix>
+        Fix CVE-2014-0050, a denial of service with a malicious, malformed
+        Content-Type header and multipart request processing. Fixed by merging
+        latest code (r1565159) from Commons FileUpload. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.1 (markt)" rtext="beta, 2014-02-02">
+  <subsection name="Catalina">
+    <changelog>
+      <fix>
+        Change default value of <code>xmlBlockExternal</code> attribute of
+        Context. It is <code>true</code> now. (kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <fix>
+        Correct regression in the fix for <bug>55996</bug> that meant that
+        asynchronous requests might timeout too early. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <fix>
+        Change default value of the <code>blockExternal</code> attribute of
+        JspC task. The default value is <code>true</code>. Add support for
+        <code>-no-blockExternal</code> switch when JspC is run as a
+        standalone application. (kkolinko)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="WebSocket">
+    <changelog>
+      <fix>
+        Do not return an empty string for the
+        <code>Sec-WebSocket-Protocol</code> HTTP header when no sub-protocol has
+        been requested or no sub-protocol could be agreed as RFC6455 requires
+        that no <code>Sec-WebSocket-Protocol</code> header is returned in this
+        case. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+</section>
+<section name="Tomcat 8.0.0 (markt)" rtext="not released">
+  <subsection name="Catalina">
+    <changelog>
+      <add>
+        Implement JSR 340 - Servlet 3.1. The JSR 340 implementation includes
+        contributions from Nick Williams and Jeremy Boynes. (markt)
+      </add>
+      <add>
+        Implement JSR 245 MR2 - JSP 2.3. (markt)
+      </add>
+      <add>
+        Implement JSR 341 - Unified Expression Language 3.0. (markt)
+      </add>
+      <add>
+        Implement JSR 356 - WebSockets. The JSR 356 implementation includes
+        contributions from Nick Williams, Rossen Stoyanchev and Niki Dokovski.
+        (markt)
+      </add>
+      <update>
+        <bug>46727</bug>: Refactor default servlet to make it easier to
+        sub-class to implement finer grained control of the file encoding. Based
+        on a patch by Fred Toth. (markt)
+      </update>
+      <add>
+        <bug>45995</bug>: Align Tomcat with Apache httpd and perform MIME type
+        mapping based on file extension in a case insensitive manner. (markt)
+      </add>
+      <scode>
+        Remove duplicate code that converted a Host&apos;s appBase attribute to
+        a canonical file. (markt)
+      </scode>
+      <scode>
+        <bug>51408</bug>: Replace calls to <code>Charset.defaultCharset()</code>
+        with an explicit reference to the ISO-8859-1 Charset. (markt)
+      </scode>
+      <scode>
+        Refactor initialization code to use a single, consistent approach to
+        determining the Catalina home (binary) and base (instance) directories.
+        The search order for home is <code>catalina.home</code> system property,
+        parent of current directory if boootstrap.jar is present and finally
+        current working directory. The search order for Catalina base is
+        <code>catalina.base</code> system property falling back to the value for
+        Catalina home. (markt)
+      </scode>
+      <update>
+        <bug>52092</bug>: JULI now uses the <code>OneLineFormatter</code> and
+        <code>AsyncFileHandler</code> by default. (markt)
+      </update>
+      <fix>
+        <bug>52558</bug>: Refactor <code>CometConnectionManagerValve</code> so
+        that it does not prevent the session from being serialized in when
+        running in a cluster. (markt)
+      </fix>
+      <fix>
+        <bug>52767</bug>: Remove reference to MySQL specific autoReconnect
+        property in <code>JDBCAccessLogValve</code>. (markt)
+      </fix>
+      <scode>
+        Make the Mapper type-safe. Hosts, Contexts and Wrappers are no
+        longer handled as plain objects, instead they keep their type.
+        Code using the Mapper doesn't need to cast objects returned by
+        the mapper. (rjung)
+      </scode>
+      <scode>
+        Move Manager, Loader and Resources from Container to Context since
+        Context is the only place they are used. The documentation already
+        states (and has done for some time) that Context is the only valid
+        location for these nested components. (markt)
+      </scode>
+      <scode>
+        Move the Mapper from the Connector to the Service since the Mapper is
+        identical for all Connectors of a given Service and it is common for
+        there to be multiple Connectors for a Service (http, https and ajp).
+        This means there is now only ever one Mapper per Service rather than
+        possibly multiple identically configured Mapper objects. (markt)
+      </scode>
+      <scode>
+        Remove the per Context Mapper objects and use the Mapper from the
+        Service. This removes the need to maintain two copies of the mappings
+        for Servlets and Filters. (markt)
+      </scode>
+      <add>
+        Implement a new Resources implementation that merges Aliases,
+        VirtualLoader, VirtualDirContext, JAR resources and external
+        repositories into a single framework rather than a separate one for each
+        feature. (markt)
+      </add>
+      <add>
+        URL rewrite valve, similar in functionality to mod_rewrite. (remm)
+      </add>
+      <add>
+        Port storeconfig functionality, which can persist to server.xml and
+        context.xml runtime container configuration changes. (remm)
+      </add>
+      <add>
+        <bug>54095</bug>: Add support to the Default Servlet for serving
+        gzipped versions of static resources directly from disk as an
+        alternative to Tomcat compressing them on each request. Patch by
+        Philippe Marschall. (markt)
+      </add>
+      <fix>
+        <bug>54708</bug>: Change the name of the working directory for the ROOT
+        application (located under $CATALINA_BASE/work by default) from _ to
+        ROOT. (markt)
+      </fix>
+      <add>
+        Change default configuration so that a change to the global web.xml file
+        will trigger a reload of all web applications. (markt)
+      </add>
+      <fix>
+        <bug>55101</bug>: Make BASIC authentication more tolerant of whitespace.
+        Patch provided by Brian Burch. (markt)
+      </fix>
+      <fix>
+        <bug>55166</bug>: Move JSP descriptor and tag library descriptor schemas
+        to servlet-api.jar to enable relative references between the schemas to
+        be correctly resolved. (markt)
+      </fix>
+      <scode>
+        Refactor the descriptor parsing code into a separate module that can be
+        used by both Catalina and Jasper. Includes patches provided by Jeremy
+        Boynes. (violetagg/markt)
+      </scode>
+      <scode>
+        <bug>55246</bug>: Move TLD scanning to a ServletContainerInitializer
+        provided by Jasper. Includes removal of TldConfig lifecycle listener and
+        associated Context properties. (jboynes)
+      </scode>
+      <add>
+        <bug>55317</bug>: Facilitate weaving by allowing ClassFileTransformer to
+        be added to WebppClassLoader. Patch by Nick Williams. (markt)
+      </add>
+      <fix>
+        <bug>55620</bug>: Enable Tomcat to start when either $CATALINA_HOME
+        and/or $CATALINA_BASE contains a comma character. Prevent Tomcat from
+        starting when $CATALINA_HOME and/or $CATALINA_BASE contains a semi-colon
+        on Windows. Prevent Tomcat from starting when $CATALINA_HOME and/or
+        $CATALINA_BASE contains a colon on Linux/FreeBSD/etc. (markt)
+      </fix>
+      <scode>
+        Initialize the JSP runtime in Jasper's initializer to avoid need for a
+        Jasper-specific lifecycle listener. <code>JasperListener</code> has been
+        removed. (jboynes)
+      </scode>
+      <fix>
+        Change ordering of elements of JMX objects names so components are
+        grouped more logically in JConsole. Generally, components are now
+        grouped by Host and then by Context. (markt)
+      </fix>
+      <add>
+        Context listener to allow better EE and framework integration. (remm)
+      </add>
+    </changelog>
+  </subsection>
+  <subsection name="Coyote">
+    <changelog>
+      <add>
+        Experimental support for SPDY. Includes contributions from Sheldon Shao.
+        (costin)
+      </add>
+      <scode>
+        The default connector is now the Java NIO connector even when specifying
+        HTTP/1.1 as protocol (fhanik)
+      </scode>
+      <scode>
+        Update default value of pollerThreadCount for the NIO connector. The new
+        default value will never go above 2 regardless of available processors.
+        (fhanik)
+      </scode>
+      <fix>
+        <bug>54010</bug>: Remove some unnecessary code (duplicate calls to
+        configure the scheme as https for AJP requests originally received over
+        HTTPS). (markt)
+      </fix>
+      <scode>
+        Refactor char encoding/decoding using NIO APIs. (remm)
+      </scode>
+      <update>
+        Change the default URIEncoding for all connectors from ISO-8859-1 to
+        UTF-8. (markt)
+      </update>
+    </changelog>
+  </subsection>
+  <subsection name="Jasper">
+    <changelog>
+      <scode>
+        Simplify API of <code>ErrorDispatcher</code> class by using varargs.
+        (kkolinko)
+      </scode>
+      <scode>
+        Update Jasper to use the new common web.xml parsing code. Includes
+        patches by Jeremy Boynes. (markt/violetagg)
+      </scode>
+      <add>
+        Create test cases for JspC. Patch by Jeremy Boynes. (markt)
+      </add>
+      <scode>
+        <bug>55246</bug>: TLD scanning is now performed by JasperInitializer
+        (a ServletContainerInitializer) removing the need for support within the
+        Servlet container itself. The scan is now performed only once rather than
+        in two passes reducing startup time. (jboynes)
+      </scode>
+      <fix>
+        <bug>55251</bug>: Do not allow JspC task to fail silently if the web.xml
+        or web.xml fragment can not be generated. (markt)
+      </fix>
+    </changelog>
+  </subsection>
+  <subsection name="Cluster">
+    <changelog>
+      <scode>
+        Remove unused JvmRouteSessionIDBinderListener and SessionIDMessage.
+        (kfujino)
+      </scode>
+      <scode>
+        Modify method signature in ReplicationValve. Cluster instance is not
+        necessary to argument of method. (kfujino)
+      </scode>
+      <scode>
+        Remove unused <code>expireSessionsOnShutdown</code> attribute in
+        <code>org.apache.catalina.ha.session.BackupManager</code>. (kfujino)
+      </scode>
+    </changelog>
+  </subsection>
+  <subsection name="Web applications">
+    <changelog>
+      <add>
+        Extend the diagnostic information provided by the Manager web
+        application to include details of the configured SSL ciphers suites for
+        each connector. (markt)
+      </add>
+      <update>
+        <bug>48550</bug>: Update examples web application to use UTF-8. (markt)
+      </update>
+      <update>
+        <bug>55383</bug>: Improve the design and correct the HTML markup of
+        the documentation web application. Patches provided by Konstantin
+        Preißer. (markt)
+      </update>
+    </changelog>
+  </subsection>
+  <subsection name="Tribes">
+    <changelog>
+      <scode>
+        Refactor <code>AbstractReplicatedMap</code> to use generics. A key
+        side-effect of this is that the class now implements
+        <code>Map&lt;K,V&gt;</code> rather than extends
+        <code>ConcurrentMap</code>. (markt)
+      </scode>
+    </changelog>
+  </subsection>
+  <subsection name="Other">
+    <changelog>
+      <scode>
+        Remove unused, deprecated code. (markt)
+      </scode>
+      <scode>
+        Remove static info String and associated getInfo() method where present.
         (markt)
       </scode>
+      <update>
+        (<rev>1353242</rev>, <rev>1353410</rev>):
+        Remove Ant tasks <code>jasper2</code> and <code>jkstatus</code>.
+        The correct names are <code>jasper</code> and <code>jkupdate</code>.
+        (kkolinko)
+      </update>
+      <fix>
+        <bug>53529</bug>: Clean-up the handling of
+        <code>InterruptedException</code> throughout the code base. (markt)
+      </fix>
+      <add>
+        <bug>54899</bug>: Provide an initial implementation of NetBeans support.
+        Patch provided by Brian Burch. (markt)
+      </add>
+      <fix>
+        <bug>55166</bug>: Move the JSP descriptor and tag library descriptor
+        schema defintion files from jsp-api.jar to servlet-api.jar so relative
+        includes between the J2EE, Servlet and JSP schemas are correctly
+        resolved. (markt)
+      </fix>
+      <fix>
+        <bug>55372</bug>: When starting Tomcat with the <code>jpda</code> option
+        to enable remote debugging, by default only listen on localhost for
+        connections from a debugger. Prior to this change, Tomcat listened on
+        all known addresses. (markt)
+      </fix>
     </changelog>
   </subsection>
 </section>
diff --git a/webapps/docs/config/ajp.xml b/webapps/docs/config/ajp.xml
index dacf4e3..be124b7 100644
--- a/webapps/docs/config/ajp.xml
+++ b/webapps/docs/config/ajp.xml
@@ -166,9 +166,11 @@
         <code>PATH</code> (Windows) or <code>LD_LIBRARY_PATH</code> (on most unix
         systems) environment variables contain the Tomcat native library, the
         native/APR connector will be used. If the native library cannot be
-        found, the Java NIO based connector will be used.<br/>
+        found, the Java based connector will be used.<br/>
         To use an explicit protocol rather than rely on the auto-switching
         mechanism described above, the following values may be used:<br/>
+        <code>org.apache.coyote.ajp.AjpProtocol</code>
+        - blocking Java connector<br/>
         <code>org.apache.coyote.ajp.AjpNioProtocol</code>
         - non blocking Java NIO connector.<br/>
         <code>org.apache.coyote.ajp.AjpNio2Protocol</code>
@@ -260,9 +262,9 @@
 
   <p>To use AJP, you must specify the protocol attribute (see above).</p>
 
-  <p>The standard AJP connectors (NIO, NIO2 and APR/native) all support the
-  following attributes in addition to the common Connector attributes listed
-  above.</p>
+  <p>The standard AJP connectors (BIO, NIO, NIO2 and APR/native) all support
+  the following attributes in addition to the common Connector attributes
+  listed above.</p>
 
   <attributes>
 
@@ -341,8 +343,9 @@
     <attribute name="executorTerminationTimeoutMillis" required="false">
       <p>The time that the private internal executor will wait for request
       processing threads to terminate before continuing with the process of
-      stopping the connector. If not set, the default is <code>5000</code> (5
-      seconds).</p>
+      stopping the connector. If not set, the default is <code>0</code> (zero)
+      for the BIO connector and <code>5000</code> (5 seconds) for the NIO, NIO2
+      and APR/native connectors.</p>
     </attribute>
 
     <attribute name="keepAliveTimeout" required="false">
@@ -361,7 +364,10 @@
       start accepting and processing new connections again. Note that once the
       limit has been reached, the operating system may still accept connections
       based on the <code>acceptCount</code> setting. The default value varies by
-      connector type. For NIO and NIO2 the default is <code>10000</code>.
+      connector type. For BIO the default is the value of
+      <strong>maxThreads</strong> unless an <a href="executor.html">Executor</a>
+      is used in which case the default will be the value of maxThreads from the
+      executor. For NIO and NIO2 the default is <code>10000</code>.
       For APR/native, the default is <code>8192</code>.</p>
       <p>Note that for APR/native on Windows, the configured value will be
       reduced to the highest multiple of 1024 that is less than or equal to
@@ -437,7 +443,7 @@
 
   <subsection name="Java TCP socket attributes">
 
-    <p>The NIO and NIO2 implementation support the following Java TCP socket
+    <p>The BIO, NIO and NIO2 implementation support the following Java TCP socket
     attributes in addition to the common Connector and HTTP attributes listed
     above.</p>
 
@@ -743,54 +749,63 @@
     <table class="defaultTable" style="text-align: center;">
       <tr>
         <th />
+        <th>Java Blocking Connector<br />BIO</th>
         <th>Java Nio Connector<br />NIO</th>
         <th>Java Nio2 Connector<br />NIO2</th>
         <th>APR/native Connector<br />APR</th>
       </tr>
       <tr>
         <th style="text-align: left;">Classname</th>
+        <td><code class="noHighlight">AjpProtocol</code></td>
         <td><code class="noHighlight">AjpNioProtocol</code></td>
         <td><code class="noHighlight">AjpNio2Protocol</code></td>
         <td><code class="noHighlight">AjpAprProtocol</code></td>
       </tr>
       <tr>
         <th style="text-align: left;">Tomcat Version</th>
+        <td>3.x onwards</td>
         <td>7.x onwards</td>
         <td>8.x onwards</td>
         <td>5.5.x onwards</td>
       </tr>
       <tr>
         <th style="text-align: left;">Support Polling</th>
+        <td>NO</td>
         <td>YES</td>
         <td>YES</td>
         <td>YES</td>
       </tr>
       <tr>
         <th style="text-align: left;">Polling Size</th>
+        <td>N/A</td>
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
       </tr>
       <tr>
         <th style="text-align: left;">Read Request Headers</th>
+        <td>Blocking</td>
         <td>Sim Blocking</td>
         <td>Blocking</td>
         <td>Blocking</td>
       </tr>
       <tr>
         <th style="text-align: left;">Read Request Body</th>
+        <td>Blocking</td>
         <td>Sim Blocking</td>
         <td>Blocking</td>
         <td>Blocking</td>
       </tr>
       <tr>
         <th style="text-align: left;">Write Response</th>
+        <td>Blocking</td>
         <td>Sim Blocking</td>
         <td>Blocking</td>
         <td>Blocking</td>
       </tr>
       <tr>
         <th style="text-align: left;">Wait for next Request</th>
+        <td>Blocking</td>
         <td>Non Blocking</td>
         <td>Non Blocking</td>
         <td>Non Blocking</td>
@@ -800,6 +815,7 @@
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
+        <td><code class="noHighlight">maxConnections</code></td>
       </tr>
     </table>
 
diff --git a/webapps/docs/config/cluster-manager.xml b/webapps/docs/config/cluster-manager.xml
index 8e90ec0..9c56126 100644
--- a/webapps/docs/config/cluster-manager.xml
+++ b/webapps/docs/config/cluster-manager.xml
@@ -97,6 +97,13 @@
         varied by a servlet via the
         <code>setMaxInactiveInterval</code> method of the <code>HttpSession</code> object.</p>
       </attribute>
+      <attribute name="sessionIdLength" required="false">
+       <p>The length of session ids created by this Manager, measured in bytes,
+        excluding subsequent conversion to a hexadecimal string and
+        excluding any JVM route information used for load balancing.
+        This attribute is deprecated. Set the length on a nested
+        <strong>SessionIdGenerator</strong> element instead.</p>
+      </attribute>
       <attribute name="processExpiresFrequency" required="false">
         <p>Frequency of the session expiration, and related manager operations.
         Manager operations will be done once for the specified amount of
diff --git a/webapps/docs/config/http.xml b/webapps/docs/config/http.xml
index e9942f4..b147b75 100644
--- a/webapps/docs/config/http.xml
+++ b/webapps/docs/config/http.xml
@@ -158,15 +158,17 @@
     <attribute name="protocol" required="false">
       <p>Sets the protocol to handle incoming traffic. The default value is
         <code>HTTP/1.1</code> which uses an auto-switching mechanism to select
-        either a Java NIO based connector or an APR/native based connector.
+        either a non blocking Java NIO based connector or an APR/native based connector.
         If the <code>PATH</code> (Windows) or <code>LD_LIBRARY_PATH</code> (on
         most unix systems) environment variables contain the Tomcat native
         library, the APR/native connector will be used. If the native library
-        cannot be found, the Java NIO based connector will be used. Note
+        cannot be found, the non blocking Java based connector will be used. Note
         that the APR/native connector has different settings for HTTPS than the
         Java connectors.<br/>
         To use an explicit protocol rather than rely on the auto-switching
         mechanism described above, the following values may be used:<br/>
+        <code>org.apache.coyote.http11.Http11Protocol</code> -
+              blocking Java connector<br/>
         <code>org.apache.coyote.http11.Http11NioProtocol</code> -
               non blocking Java NIO connector<br/>
         <code>org.apache.coyote.http11.Http11Nio2Protocol</code> -
@@ -259,9 +261,9 @@
 
   <subsection name="Standard Implementation">
 
-  <p>The standard HTTP connectors (NIO, NIO2 and APR/native) all support the
-  following attributes in addition to the common Connector attributes listed
-  above.</p>
+  <p>The standard HTTP connectors (BIO, NIO, NIO2 and APR/native) all support
+  the following attributes in addition to the common Connector attributes
+  listed above.</p>
 
   <attributes>
 
@@ -380,8 +382,9 @@
     <attribute name="executorTerminationTimeoutMillis" required="false">
       <p>The time that the private internal executor will wait for request
       processing threads to terminate before continuing with the process of
-      stopping the connector. If not set, the default is  <code>5000</code> (5
-      seconds).</p>
+      stopping the connector. If not set, the default is <code>0</code> (zero)
+      for the BIO connector and <code>5000</code> (5 seconds) for the NIO,
+      NIO2 and APR/native connectors.</p>
     </attribute>
 
     <attribute name="keepAliveTimeout" required="false">
@@ -401,7 +404,10 @@
       start accepting and processing new connections again. Note that once the
       limit has been reached, the operating system may still accept connections
       based on the <code>acceptCount</code> setting. The default value varies by
-      connector type. For NIO and NIO2 the default is <code>10000</code>.
+      connector type. For BIO the default is the value of
+      <strong>maxThreads</strong> unless an <a href="executor.html">Executor</a>
+      is used in which case the default will be the value of maxThreads from the
+      executor. For NIO and NIO2 the default is <code>10000</code>.
       For APR/native, the default is <code>8192</code>.</p>
       <p>Note that for APR/native on Windows, the configured value will be
       reduced to the highest multiple of 1024 that is less than or equal to
@@ -498,6 +504,12 @@
       </p>
     </attribute>
 
+    <attribute name="socketBuffer" required="false">
+      <p>The size (in bytes) of the buffer to be provided for socket
+      output buffering. -1 can be specified to disable the use of a buffer.
+      By default, a buffers of 9000 bytes will be used.</p>
+    </attribute>
+
     <attribute name="SSLEnabled" required="false">
       <p>Use this attribute to enable SSL traffic on a connector.
       To turn on SSL handshake/encryption/decryption on a connector
@@ -540,7 +552,7 @@
 
   <subsection name="Java TCP socket attributes">
 
-    <p>The NIO and NIO2 implementation support the following Java TCP
+    <p>The BIO, NIO and NIO2 implementation support the following Java TCP
     socket attributes in addition to the common Connector and HTTP attributes
     listed above.</p>
 
@@ -614,6 +626,24 @@
     </attributes>
   </subsection>
 
+  <subsection name="BIO specific configuration">
+
+    <p>The following attributes are specific to the BIO connector.</p>
+
+    <attributes>
+
+      <attribute name="disableKeepAlivePercentage" required="false">
+        <p>The percentage of processing threads that have to be in use before
+        HTTP keep-alives are disabled to improve scalability. Values less than
+        <code>0</code> will be changed to <code>0</code> and values greater than
+        <code>100</code> will be changed to <code>100</code>. If not specified,
+        the default value is <code>75</code>.</p>
+      </attribute>
+
+    </attributes>
+
+  </subsection>
+
   <subsection name="NIO specific configuration">
 
     <p>The following attributes are specific to the NIO connector.</p>
@@ -647,6 +677,11 @@
         default value is <code>1000</code> milliseconds.</p>
       </attribute>
 
+      <attribute name="useComet" required="false">
+        <p>(bool)Whether to allow comet servlets or not. Default value is
+        <code>true</code>.</p>
+      </attribute>
+
       <attribute name="useSendfile" required="false">
         <p>(bool)Use this attribute to enable or disable sendfile capability.
         The default value is <code>true</code>. Note that the use of sendfile
@@ -777,6 +812,11 @@
         The default value is <code>false</code>.</p>
       </attribute>
 
+      <attribute name="useComet" required="false">
+        <p>(bool)Whether to allow comet servlets or not. Default value is
+        <code>true</code>.</p>
+      </attribute>
+
       <attribute name="useSendfile" required="false">
         <p>(bool)Use this attribute to enable or disable sendfile capability.
         The default value is <code>true</code>. Note that the use of sendfile
@@ -924,6 +964,11 @@
         this priority means.</p>
       </attribute>
 
+      <attribute name="useComet" required="false">
+        <p>(bool)Whether to allow comet servlets or not. Default value is
+        <code>true</code>.</p>
+      </attribute>
+
       <attribute name="useSendfile" required="false">
         <p>(bool)Use this attribute to enable or disable sendfile capability.
         The default value is <code>true</code>. Note that the use of sendfile
@@ -996,7 +1041,7 @@
   attributes to the values <code>https</code> and <code>true</code>
   respectively, to pass correct information to the servlets.</p>
 
-  <p>The NIO and NIO2 connectors use the JSSE SSL whereas the APR/native
+  <p>The BIO, NIO and NIO2 connectors use the JSSE SSL whereas the APR/native
   connector uses OpenSSL. Therefore, in addition to using different attributes
   to configure SSL, the APR/native connector also requires keys and certificates
   to be provided in a different format.</p>
@@ -1004,9 +1049,9 @@
   <p>For more information, see the
   <a href="../ssl-howto.html">SSL Configuration HOW-TO</a>.</p>
 
-  <subsection name="SSL Support - NIO and NIO2">
+  <subsection name="SSL Support - BIO, NIO and NIO2">
 
-  <p>The NIO and NIO2 connectors use the following attributes to configure SSL:
+  <p>The BIO, NIO and NIO2 connectors use the following attributes to configure SSL:
   </p>
 
   <attributes>
@@ -1359,54 +1404,63 @@
     <table class="defaultTable" style="text-align: center;">
       <tr>
         <th />
+        <th>Java Blocking Connector<br />BIO</th>
         <th>Java Nio Connector<br />NIO</th>
         <th>Java Nio2 Connector<br />NIO2</th>
         <th>APR/native Connector<br />APR</th>
       </tr>
       <tr>
         <th style="text-align: left;">Classname</th>
+        <td><code class="noHighlight">Http11Protocol</code></td>
         <td><code class="noHighlight">Http11NioProtocol</code></td>
         <td><code class="noHighlight">Http11Nio2Protocol</code></td>
         <td><code class="noHighlight">Http11AprProtocol</code></td>
       </tr>
       <tr>
         <th style="text-align: left;">Tomcat Version</th>
+        <td>3.x onwards</td>
         <td>6.x onwards</td>
         <td>8.x onwards</td>
         <td>5.5.x onwards</td>
       </tr>
       <tr>
         <th style="text-align: left;">Support Polling</th>
+        <td>NO</td>
         <td>YES</td>
         <td>YES</td>
         <td>YES</td>
       </tr>
       <tr>
         <th style="text-align: left;">Polling Size</th>
+        <td>N/A</td>
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
       </tr>
       <tr>
         <th style="text-align: left;">Read HTTP Request</th>
+        <td>Blocking</td>
         <td>Non Blocking</td>
         <td>Non Blocking</td>
         <td>Blocking</td>
       </tr>
       <tr>
         <th style="text-align: left;">Read HTTP Body</th>
+        <td>Blocking</td>
         <td>Sim Blocking</td>
         <td>Blocking</td>
         <td>Blocking</td>
       </tr>
       <tr>
         <th style="text-align: left;">Write HTTP Response</th>
+        <td>Blocking</td>
         <td>Sim Blocking</td>
         <td>Blocking</td>
         <td>Blocking</td>
       </tr>
       <tr>
         <th style="text-align: left;">Wait for next Request</th>
+        <td>Blocking</td>
         <td>Non Blocking</td>
         <td>Non Blocking</td>
         <td>Non Blocking</td>
@@ -1415,10 +1469,12 @@
         <th style="text-align: left;">SSL Support</th>
         <td>Java SSL</td>
         <td>Java SSL</td>
+        <td>Java SSL</td>
         <td>OpenSSL</td>
       </tr>
       <tr>
         <th style="text-align: left;">SSL Handshake</th>
+        <td>Blocking</td>
         <td>Non blocking</td>
         <td>Non blocking</td>
         <td>Blocking</td>
@@ -1428,6 +1484,7 @@
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
         <td><code class="noHighlight">maxConnections</code></td>
+        <td><code class="noHighlight">maxConnections</code></td>
       </tr>
     </table>
 
diff --git a/webapps/docs/config/manager.xml b/webapps/docs/config/manager.xml
index 0e774f5..f13253d 100644
--- a/webapps/docs/config/manager.xml
+++ b/webapps/docs/config/manager.xml
@@ -99,6 +99,15 @@
         varied by a servlet via the
         <code>setMaxInactiveInterval</code> method of the <code>HttpSession</code> object.</p>
       </attribute>
+
+      <attribute name="sessionIdLength" required="false">
+       <p>The length of session ids created by this Manager, measured in bytes,
+        excluding subsequent conversion to a hexadecimal string and
+        excluding any JVM route information used for load balancing.
+        This attribute is deprecated. Set the length on a nested
+        <strong>SessionIdGenerator</strong> element instead.</p>
+      </attribute>
+
     </attributes>
 
   </subsection>
diff --git a/webapps/docs/config/project.xml b/webapps/docs/config/project.xml
index 349f258..9001254 100644
--- a/webapps/docs/config/project.xml
+++ b/webapps/docs/config/project.xml
@@ -15,10 +15,10 @@
   See the License for the specific language governing permissions and
   limitations under the License.
 -->
-<project name="Apache Tomcat 9 Configuration Reference"
+<project name="Apache Tomcat 8 Configuration Reference"
         href="http://tomcat.apache.org/">
 
-  <title>Apache Tomcat 9 Configuration Reference</title>
+  <title>Apache Tomcat 8 Configuration Reference</title>
 
   <logo href="/images/tomcat.gif">
     The Apache Tomcat Servlet/JSP Container
diff --git a/webapps/docs/config/systemprops.xml b/webapps/docs/config/systemprops.xml
index 358dd80..605562b 100644
--- a/webapps/docs/config/systemprops.xml
+++ b/webapps/docs/config/systemprops.xml
@@ -496,33 +496,29 @@
          <code>-Dorg.apache.juli.formatter=org.apache.juli.OneLineFormatter</code></p>
     </property>
 
+    <property name="org.apache.juli. AsyncOverflowDropType">
+      <p>When the memory limit of records has been reached the system needs to determine what action to take.
+         Currently there are three actions that can be taken:
+      </p>
+         <ul>
+           <li><code>int OVERFLOW_DROP_LAST = 1</code> - the record that caused the overflow will be dropped and not logged</li>
+           <li><code>int OVERFLOW_DROP_FIRST = 2</code> - the record that is next in line to be logged will be dropped to make room for the latest record on the queue</li>
+           <li><code>int OVERFLOW_DROP_FLUSH = 3</code> - suspend the thread while the queue empties out and flushes the entries to the write buffer</li>
+           <li><code>int OVERFLOW_DROP_CURRENT = 4</code> - drop the current log entry</li>
+         </ul>
+      <p>The default value is <code>1</code> (OVERFLOW_DROP_LAST).</p>
+    </property>
+
     <property name="org.apache.juli. AsyncMaxRecordCount">
-      <p>The maximum number of log records that the JULI AsyncFileHandler will queue in memory.
-         New records are added to the queue and get asynchronously removed from the queue
-         and written to the files by a single writer thread.
-         When the queue is full and a new record is being logged
-         the log record will be handled based on the <code>org.apache.juli.AsyncOverflowDropType</code> setting.</p>
+      <p>The max number of log records that the async logger will keep in memory. When this limit is reached and a new record is being logged by the
+         JULI framework the system will take an action based on the <code>org.apache.juli.AsyncOverflowDropType</code> setting.</p>
       <p>The default value is <code>10000</code> records.
          This number represents the global number of records, not on a per handler basis.
       </p>
     </property>
 
-    <property name="org.apache.juli. AsyncOverflowDropType">
-      <p>When the queue of log records of the JULI AsyncFileHandler is full,
-         new log records are handled according to the following setting:
-      </p>
-         <ul>
-           <li><code>1</code> - the newest record in the queue will be dropped and not logged</li>
-           <li><code>2</code> - the oldest record in the queue will be dropped and not logged</li>
-           <li><code>3</code> - suspend the logging thread until older records got written to the log file and the queue is no longer full.
-                                This is the only setting that ensures that no messages get lost.</li>
-           <li><code>4</code> - drop the current log record</li>
-         </ul>
-      <p>The default value is <code>1</code> (drop the newest record in the queue).</p>
-    </property>
-
     <property name="org.apache.juli. AsyncLoggerPollInterval">
-      <p>The poll interval in milliseconds for the asynchronous logger thread.
+      <p>The poll interval in milliseconds for the asynchronous logger thread in milliseconds.
          If the log queue is empty, the async thread will issue a poll(poll interval)
          in order to not wake up too often.</p>
       <p>The default value is <code>1000</code> milliseconds.</p>
@@ -597,7 +593,7 @@
 
   <properties>
 
-    <property name="org.apache.tomcat. websocket.ALLOW_UNSUPPORTED_EXTENSIONS">
+    <property name="org.apache.tomcat .websocket.ALLOW_UNSUPPORTED_EXTENSIONS">
       <p>If <code>true</code>, allow unknown extensions to be declared by
       the user.</p>
       <p>The default value is <code>false</code>.</p>
diff --git a/webapps/docs/config/valve.xml b/webapps/docs/config/valve.xml
index edaef0e..af6ed60 100644
--- a/webapps/docs/config/valve.xml
+++ b/webapps/docs/config/valve.xml
@@ -455,15 +455,15 @@
 <section name="Access Control">
 
 
-<subsection name="Remote Address Valve">
+<subsection name="Remote Address Filter">
 
   <subsection name="Introduction">
 
-    <p>The <strong>Remote Address Valve</strong> allows you to compare the
+    <p>The <strong>Remote Address Filter</strong> allows you to compare the
     IP address of the client that submitted this request against one or more
     <em>regular expressions</em>, and either allow the request to continue
     or refuse to process the request from this client.  A Remote Address
-    Valve can be associated with any Catalina container
+    Filter can be associated with any Catalina container
     (<a href="engine.html">Engine</a>, <a href="host.html">Host</a>, or
     <a href="context.html">Context</a>), and must accept any request
     presented to this container for processing before it will be passed on.</p>
@@ -489,13 +489,13 @@
     will be <code>0:0:0:0:0:0:0:1</code> instead of the more widely used
     <code>::1</code>. Consult your access logs for the actual value.</p>
 
-    <p>See also: <a href="#Remote_Host_Valve">Remote Host Valve</a>,
+    <p>See also: <a href="#Remote_Host_Filter">Remote Host Filter</a>,
     <a href="#Remote_IP_Valve">Remote IP Valve</a>.</p>
   </subsection>
 
   <subsection name="Attributes">
 
-    <p>The <strong>Remote Address Valve</strong> supports the following
+    <p>The <strong>Remote Address Filter</strong> supports the following
     configuration attributes:</p>
 
     <attributes>
@@ -584,15 +584,15 @@
 </subsection>
 
 
-<subsection name="Remote Host Valve">
+<subsection name="Remote Host Filter">
 
   <subsection name="Introduction">
 
-    <p>The <strong>Remote Host Valve</strong> allows you to compare the
+    <p>The <strong>Remote Host Filter</strong> allows you to compare the
     hostname of the client that submitted this request against one or more
     <em>regular expressions</em>, and either allow the request to continue
     or refuse to process the request from this client.  A Remote Host
-    Valve can be associated with any Catalina container
+    Filter can be associated with any Catalina container
     (<a href="engine.html">Engine</a>, <a href="host.html">Host</a>, or
     <a href="context.html">Context</a>), and must accept any request
     presented to this container for processing before it will be passed on.</p>
@@ -615,13 +615,13 @@
     to return proper host names, you have to enable "DNS lookups" feature on
     a <strong>Connector</strong>.</p>
 
-    <p>See also: <a href="#Remote_Address_Valve">Remote Address Valve</a>,
+    <p>See also: <a href="#Remote_Address_Filter">Remote Address Filter</a>,
     <a href="http.html">HTTP Connector</a> configuration.</p>
   </subsection>
 
   <subsection name="Attributes">
 
-    <p>The <strong>Remote Host Valve</strong> supports the following
+    <p>The <strong>Remote Host Filter</strong> supports the following
     configuration attributes:</p>
 
     <attributes>
diff --git a/webapps/docs/jasper-howto.xml b/webapps/docs/jasper-howto.xml
index edfcae1..cb33730 100644
--- a/webapps/docs/jasper-howto.xml
+++ b/webapps/docs/jasper-howto.xml
@@ -106,10 +106,10 @@
 to the <code>CLASSPATH</code> environment variable.</li>
 
 <li><strong>compilerSourceVM</strong> - What JDK version are the source files
-compatible with? (Default value: <code>1.8</code>)</li>
+compatible with? (Default value: <code>1.7</code>)</li>
 
 <li><strong>compilerTargetVM</strong> - What JDK version are the generated files
-compatible with? (Default value: <code>1.8</code>)</li>
+compatible with? (Default value: <code>1.7</code>)</li>
 
 <li><strong>development</strong> - Is Jasper used in development mode? If true,
 the frequency at which JSPs are checked for modification may be specified via
diff --git a/webapps/docs/manager-howto.xml b/webapps/docs/manager-howto.xml
index 3b159f8..fd0b0ff 100644
--- a/webapps/docs/manager-howto.xml
+++ b/webapps/docs/manager-howto.xml
@@ -862,7 +862,7 @@
 <source>http://localhost:8080/manager/text/sslConnectorCiphers</source>
 
 <p>The SSL Connector/Ciphers diagnostic lists the SSL/TLS ciphers that are currently
-configured for each connector. For NIO and NIO2, the names of the individual
+configured for each connector. For BIO and NIO, the names of the individual
 cipher suites are listed. For APR, the value of SSLCipherSuite is returned.</p>
 
 <p>The response will look something like this:</p>
diff --git a/webapps/docs/project.xml b/webapps/docs/project.xml
index b0524de..047c73a 100644
--- a/webapps/docs/project.xml
+++ b/webapps/docs/project.xml
@@ -18,7 +18,7 @@
 <project name="Apache Tomcat Documentation"
         href="http://tomcat.apache.org/">
 
-    <title>Apache Tomcat 9</title>
+    <title>Apache Tomcat 8</title>
 
     <logo href="/images/tomcat.gif">
       The Apache Tomcat Servlet/JSP Container
diff --git a/webapps/docs/ssl-howto.xml b/webapps/docs/ssl-howto.xml
index 7c00486..9205b6b 100644
--- a/webapps/docs/ssl-howto.xml
+++ b/webapps/docs/ssl-howto.xml
@@ -299,6 +299,10 @@
 
 <!-- Define a HTTP/1.1 Connector on port 8443, JSSE NIO2 implementation -->
 <Connector protocol="org.apache.coyote.http11.Http11Nio2Protocol"
+           port="8443" .../>
+
+<!-- Define a HTTP/1.1 Connector on port 8443, JSSE BIO implementation -->
+<Connector protocol="org.apache.coyote.http11.Http11Protocol"
            port="8443" .../>]]></source>
 <p>Alternatively, to specify an APR connector (the APR library must be available) use:</p>
 <source><![CDATA[<!-- Define a HTTP/1.1 Connector on port 8443, APR implementation -->
@@ -355,7 +359,7 @@
 are mandatory, are documented in the SSL Support section of the
 <a href="config/http.html#SSL_Support">HTTP connector</a> configuration
 reference. Make sure that you use the correct attributes for the connector you
-are using. The NIO and NIO2 connectors use JSSE whereas the APR/native connector
+are using. The BIO, NIO and NIO2 connectors use JSSE whereas the APR/native connector
 uses APR.</p>
 
 <p>The <code>port</code> attribute is the TCP/IP
@@ -542,7 +546,7 @@
 
 }]]></source>
 
-  <p>Note: SSL session tracking is implemented for the NIO and NIO2 connectors.
+  <p>Note: SSL session tracking is implemented for the BIO, NIO and NIO2 connectors.
      It is not yet implemented for the APR connector.</p>
 
 </section>
@@ -572,8 +576,8 @@
 response.setHeader("Connection", "close");]]></source>
   <p>
     Note that this code is Tomcat specific due to the use of the
-    SSLSessionManager class. This is currently only available for the NIO and
-    NIO2 connectors, not the APR/native connector.
+    SSLSessionManager class. This is currently only available for the BIO, NIO
+    and NIO2 connectors, not the APR/native connector.
   </p>
 </section>
 
diff --git a/webapps/docs/tomcat-docs.xsl b/webapps/docs/tomcat-docs.xsl
index 636cdb2..405087e 100644
--- a/webapps/docs/tomcat-docs.xsl
+++ b/webapps/docs/tomcat-docs.xsl
@@ -37,17 +37,17 @@
   <xsl:param    name="apache-logo"         select="'/images/asf-feather.png'"/>
   <xsl:param    name="subdir"              select="''"/>
   <xsl:param    name="relative-path"       select="'.'"/>
-  <xsl:param    name="version"             select="'9.0.x'"/>
-  <xsl:param    name="majorversion"        select="'9'"/>
-  <xsl:param    name="majorminorversion"   select="'9.0'"/>
+  <xsl:param    name="version"             select="'8.0.x'"/>
+  <xsl:param    name="majorversion"        select="'8'"/>
+  <xsl:param    name="majorminorversion"   select="'8.0'"/>
   <xsl:param    name="build-date"          select="'MMM d yyyy'"/>
   <xsl:param    name="build-date-iso-8601" select="'yyyy-dd-MM'"/>
   <xsl:param    name="year"                select="'yyyy'"/>
   <xsl:param    name="buglink"             select="'http://issues.apache.org/bugzilla/show_bug.cgi?id='"/>
   <xsl:param    name="revlink"             select="'http://svn.apache.org/viewvc?view=rev&amp;rev='"/>
-  <xsl:param    name="doclink"             select="'http://tomcat.apache.org/tomcat-9.0-doc'"/>
-  <xsl:param    name="sylink"              select="'http://tomcat.apache.org/security-9.html'"/>
-  <xsl:param    name="dllink"              select="'http://tomcat.apache.org/download-90.cgi'"/>
+  <xsl:param    name="doclink"             select="'http://tomcat.apache.org/tomcat-8.0-doc'"/>
+  <xsl:param    name="sylink"              select="'http://tomcat.apache.org/security-8.html'"/>
+  <xsl:param    name="dllink"              select="'http://tomcat.apache.org/download-80.cgi'"/>
   <xsl:param    name="sitedir"             select="''"/>
   <xsl:param    name="filename"            select="'-'"/>
 
diff --git a/webapps/docs/web-socket-howto.xml b/webapps/docs/web-socket-howto.xml
index ddc164c..29b6a2a 100644
--- a/webapps/docs/web-socket-howto.xml
+++ b/webapps/docs/web-socket-howto.xml
@@ -49,6 +49,24 @@
    code</a>.</p>
 </section>
 
+<section name="Production usage">
+<p>Although the WebSocket implementation does work with any of the HTTP
+connectors, it is not recommended to the WebSocket with the BIO HTTP connector
+as the typical uses of WebSocket (large numbers of mostly idle connections) is
+not a good fit for the HTTP BIO connector which requires that one thread is
+allocated per connection regardless of whether or not the connection is idle.
+</p>
+
+<p>It has been reported (<bug>56304</bug>) that Linux can take large numbers of
+minutes to report dropped connections. When using WebSocket with the BIO HTTP
+connector this can result in threads blocking on writes for this period. This is
+likely to be undesirable. The time taken for the connection to be reported as
+dropped can be reduced by using the kernel network parameter
+<code>/proc/sys/net/ipv4/tcp_retries2</code>. Alternatively, one of the other
+HTTP connectors may be used as they utilise non-blocking IO enabling Tomcat to
+implement its own timeout mechanism to handle these cases.</p>
+</section>
+
 <section name="Tomcat WebSocket specific configuration">
 
 <p>Tomcat provides a number of Tomcat specific configuration options for
diff --git a/webapps/examples/WEB-INF/classes/chat/ChatServlet.java b/webapps/examples/WEB-INF/classes/chat/ChatServlet.java
new file mode 100644
index 0000000..7bec393
--- /dev/null
+++ b/webapps/examples/WEB-INF/classes/chat/ChatServlet.java
@@ -0,0 +1,292 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 chat;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PrintWriter;
+import java.util.ArrayList;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.apache.catalina.comet.CometEvent;
+import org.apache.catalina.comet.CometProcessor;
+
+
+/**
+ * Helper class to implement Comet functionality.
+ */
+public class ChatServlet
+    extends HttpServlet implements CometProcessor {
+
+    private static final long serialVersionUID = 1L;
+
+    private static final String CHARSET = "UTF-8";
+
+    protected final ArrayList<HttpServletResponse> connections =
+            new ArrayList<>();
+    protected transient MessageSender messageSender = null;
+
+    @Override
+    public void init() throws ServletException {
+        messageSender = new MessageSender();
+        Thread messageSenderThread =
+            new Thread(messageSender, "MessageSender[" + getServletContext().getContextPath() + "]");
+        messageSenderThread.setDaemon(true);
+        messageSenderThread.start();
+    }
+
+    @Override
+    public void destroy() {
+        connections.clear();
+        messageSender.stop();
+        messageSender = null;
+    }
+
+    /**
+     * Process the given Comet event.
+     *
+     * @param event The Comet event that will be processed
+     * @throws IOException
+     * @throws ServletException
+     */
+    @Override
+    public void event(CometEvent event)
+        throws IOException, ServletException {
+
+        // Note: There should really be two servlets in this example, to avoid
+        // mixing Comet stuff with regular connection processing
+        HttpServletRequest request = event.getHttpServletRequest();
+        HttpServletResponse response = event.getHttpServletResponse();
+
+        if (event.getEventType() == CometEvent.EventType.BEGIN) {
+            String action = request.getParameter("action");
+            if (action != null) {
+                if ("login".equals(action)) {
+                    String nickname = request.getParameter("nickname");
+                    request.getSession(true).setAttribute("nickname", nickname);
+                    response.sendRedirect("index.jsp");
+                    event.close();
+                    return;
+                }
+                String nickname = (String) request.getSession(true).getAttribute("nickname");
+                String message = request.getParameter("message");
+                messageSender.send(nickname, message);
+                response.sendRedirect("post.jsp");
+                event.close();
+                return;
+            }
+            if (request.getSession(true).getAttribute("nickname") == null) {
+                // Redirect to "login"
+                log("Redirect to login for session: " + request.getSession(true).getId());
+                response.sendRedirect("login.jsp");
+                event.close();
+                return;
+            }
+            begin(event, request, response);
+        } else if (event.getEventType() == CometEvent.EventType.ERROR) {
+            error(event, request, response);
+        } else if (event.getEventType() == CometEvent.EventType.END) {
+            end(event, request, response);
+        } else if (event.getEventType() == CometEvent.EventType.READ) {
+            read(event, request, response);
+        }
+    }
+
+    protected void begin(@SuppressWarnings("unused") CometEvent event,
+            HttpServletRequest request, HttpServletResponse response)
+        throws IOException {
+        log("Begin for session: " + request.getSession(true).getId());
+
+        response.setContentType("text/html; charset=" + CHARSET);
+
+        PrintWriter writer = response.getWriter();
+        writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
+        writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");
+        writer.println("<div>Welcome to the chat. <a href='chat'>Click here to reload this window</a></div>");
+        writer.flush();
+
+        synchronized(connections) {
+            connections.add(response);
+        }
+
+        messageSender.send("Tomcat", request.getSession(true).getAttribute("nickname") + " joined the chat.");
+    }
+
+    protected void end(CometEvent event, HttpServletRequest request, HttpServletResponse response)
+        throws IOException {
+        log("End for session: " + request.getSession(true).getId());
+        synchronized(connections) {
+            connections.remove(response);
+        }
+
+        PrintWriter writer = response.getWriter();
+        writer.println("</body></html>");
+
+        event.close();
+    }
+
+    protected void error(CometEvent event, HttpServletRequest request, HttpServletResponse response)
+        throws IOException {
+        log("Error for session: " + request.getSession(true).getId());
+        synchronized(connections) {
+            connections.remove(response);
+        }
+        event.close();
+    }
+
+    protected void read(CometEvent event, HttpServletRequest request, HttpServletResponse response)
+        throws IOException {
+        InputStream is = request.getInputStream();
+        byte[] buf = new byte[512];
+        while (is.available() > 0) {
+            log("Available: " + is.available());
+            int n = is.read(buf);
+            if (n > 0) {
+                log("Read " + n + " bytes: " + new String(buf, 0, n)
+                        + " for session: " + request.getSession(true).getId());
+            } else if (n < 0) {
+                log("End of file: " + n);
+                end(event, request, response);
+                return;
+            }
+        }
+    }
+
+    @Override
+    protected void service(HttpServletRequest request, HttpServletResponse response)
+        throws IOException, ServletException {
+        // Compatibility method: equivalent method using the regular connection model
+        response.setContentType("text/html; charset=" + CHARSET);
+        PrintWriter writer = response.getWriter();
+        writer.println("<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">");
+        writer.println("<html><head><title>JSP Chat</title></head><body bgcolor=\"#FFFFFF\">");
+        writer.println("Chat example only supports Comet processing. ");
+        writer.println("Configure a connector that supports Comet and try again.");
+        writer.println("</body></html>");
+    }
+
+
+    /**
+     * Poller class.
+     */
+    public class MessageSender implements Runnable {
+
+        protected boolean running = true;
+        protected final ArrayList<String> messages = new ArrayList<>();
+
+        public MessageSender() {
+            // Default contructor
+        }
+
+        public void stop() {
+            running = false;
+            synchronized (messages) {
+                messages.notify();
+            }
+        }
+
+        public void send(String user, String message) {
+            synchronized (messages) {
+                messages.add("[" + user + "]: " + message);
+                messages.notify();
+            }
+        }
+
+        /**
+         * The background thread that listens for incoming TCP/IP connections and
+         * hands them off to an appropriate processor.
+         */
+        @Override
+        public void run() {
+
+            // Loop until we receive a shutdown command
+            while (running) {
+                String[] pendingMessages;
+                synchronized (messages) {
+                    try {
+                        if (messages.size() == 0) {
+                            messages.wait();
+                        }
+                    } catch (InterruptedException e) {
+                        // Ignore
+                    }
+                    pendingMessages = messages.toArray(new String[0]);
+                    messages.clear();
+                }
+
+                synchronized (connections) {
+                    for (int i = 0; i < connections.size(); i++) {
+                        try {
+                            PrintWriter writer = connections.get(i).getWriter();
+                            for (int j = 0; j < pendingMessages.length; j++) {
+                                writer.println("<div>"+filter(pendingMessages[j]) + "</div>");
+                            }
+                            writer.flush();
+                        } catch (IOException e) {
+                            log("IOException sending message", e);
+                        }
+                    }
+                }
+
+            }
+
+        }
+
+    }
+
+    /**
+     * Filter the specified message string for characters that are sensitive
+     * in HTML.
+     *
+     * @param message The message string to be filtered
+     * @author Copied from org.apache.catalina.util.RequestUtil#filter(String)
+     */
+    protected static String filter(String message) {
+        if (message == null)
+            return (null);
+
+        char content[] = new char[message.length()];
+        message.getChars(0, message.length(), content, 0);
+        StringBuilder result = new StringBuilder(content.length + 50);
+        for (int i = 0; i < content.length; i++) {
+            switch (content[i]) {
+            case '<':
+                result.append("&lt;");
+                break;
+            case '>':
+                result.append("&gt;");
+                break;
+            case '&':
+                result.append("&amp;");
+                break;
+            case '"':
+                result.append("&quot;");
+                break;
+            default:
+                result.append(content[i]);
+            }
+        }
+        return (result.toString());
+    }
+}
diff --git a/webapps/examples/WEB-INF/web.xml b/webapps/examples/WEB-INF/web.xml
index fac15bf..5ce3afa 100644
--- a/webapps/examples/WEB-INF/web.xml
+++ b/webapps/examples/WEB-INF/web.xml
@@ -122,6 +122,10 @@
       <servlet-class>ServletToJsp</servlet-class>
     </servlet>
     <servlet>
+        <servlet-name>ChatServlet</servlet-name>
+        <servlet-class>chat.ChatServlet</servlet-class>
+    </servlet>
+    <servlet>
         <servlet-name>CompressionFilterTestServlet</servlet-name>
         <servlet-class>compressionFilters.CompressionFilterTestServlet</servlet-class>
     </servlet>
@@ -151,6 +155,10 @@
     </servlet>
 
     <servlet-mapping>
+        <servlet-name>ChatServlet</servlet-name>
+        <url-pattern>/servlets/chat/chat</url-pattern>
+    </servlet-mapping>
+    <servlet-mapping>
         <servlet-name>CompressionFilterTestServlet</servlet-name>
         <url-pattern>/CompressionTest</url-pattern>
     </servlet-mapping>
diff --git a/webapps/examples/servlets/chat/index.jsp b/webapps/examples/servlets/chat/index.jsp
new file mode 100644
index 0000000..a867e37
--- /dev/null
+++ b/webapps/examples/servlets/chat/index.jsp
@@ -0,0 +1,32 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+--%>
+<%@page contentType="text/html; charset=UTF-8" %>
+<% if (session.getAttribute("nickname") == null) {
+    response.sendRedirect("login.jsp");
+    return;
+}
+%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Frameset//EN" "http://www.w3.org/TR/html4/frameset.dtd">
+<html>
+<head>
+   <title>JSP Chat</title>
+</head>
+<frameset rows="1*,4*">
+  <frame name="post" src="post.jsp" scrolling="no" title="Post message">
+  <frame name="chat" src="chat" scrolling="yes" title="Chat">
+</frameset>
+</html>
diff --git a/webapps/examples/servlets/chat/login.jsp b/webapps/examples/servlets/chat/login.jsp
new file mode 100644
index 0000000..beb3b38
--- /dev/null
+++ b/webapps/examples/servlets/chat/login.jsp
@@ -0,0 +1,33 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+--%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<%@page contentType="text/html; charset=UTF-8" %>
+<html>
+<head>
+   <title>JSP Chat</title>
+</head>
+
+<body bgcolor="#FFFFFF">
+
+<form method="POST" action='chat' target="_top" name="loginForm">
+<input type="hidden" name="action" value="login">
+Nickname: <input type="text" name="nickname">
+<input type="submit">
+</form>
+
+</body>
+</html>
diff --git a/webapps/examples/servlets/chat/post.jsp b/webapps/examples/servlets/chat/post.jsp
new file mode 100644
index 0000000..f6b38e5
--- /dev/null
+++ b/webapps/examples/servlets/chat/post.jsp
@@ -0,0 +1,55 @@
+<%--
+ Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You 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.
+--%>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
+<%@page contentType="text/html; charset=UTF-8" %>
+<html>
+<head>
+   <title>JSP Chat</title>
+</head>
+
+<body bgcolor="#FFFFFF">
+
+<form method="POST" action='chat' name="postForm">
+<input type="hidden" name="action" value="post">
+Message: <input type="text" name="message">
+<input type="submit">
+</form>
+
+<br>
+<%
+  String serverName = request.getServerName();
+  if ("localhost".equals(serverName)) {
+      serverName = "127.0.0.1";
+  } else if ("127.0.0.1".equals(serverName)) {
+      serverName = "localhost";
+  }
+
+  String chatUrl = request.getScheme() + "://" + serverName + ":"
+    + request.getServerPort() + request.getContextPath()
+    + request.getServletPath();
+
+  // strip "post.jsp" from the address
+  chatUrl = chatUrl.substring(0, chatUrl.lastIndexOf("/") + 1);
+%>
+<a target="_blank" href="<%=chatUrl %>">Click to open a new chat window</a>
+<em>Note</em>: To avoid hitting the limit on the count of simultaneous
+connections to the same host, imposed by the
+<a href="http://www.w3.org/Protocols/rfc2616/rfc2616-sec8.html#sec8.1.4">HTTP specification</a>,
+the second chat window should be opened using a different URL, e.g. with
+an IP address instead of the host name.
+</body>
+</html>
diff --git a/webapps/examples/servlets/index.html b/webapps/examples/servlets/index.html
index 7299d2c..625ffa2 100644
--- a/webapps/examples/servlets/index.html
+++ b/webapps/examples/servlets/index.html
@@ -149,6 +149,20 @@
 </tr>
 
 <tr>
+  <th colspan="3">Comet processing example:<br />
+    <span style="font-weight: normal;">See the <strong>"Advanced IO"</strong> chapter in the User Guide for
+    details. This example only works with the HTTP NIO or HTTP APR/native
+    connectors as these are the only connectors that support Comet.</span></th>
+</tr>
+<tr>
+  <td>Comet Chat</td>
+  <td style="width: 30%;">
+    <a href="chat/"><img SRC="images/execute.gif" alt=""> Execute</a>
+  </td>
+  <td style="width: 30%;"></td>
+</tr>
+
+<tr>
   <th colspan="3">Servlet 3.1 Non-blocking IO examples</th>
 </tr>
 <tr>
diff --git a/webapps/examples/websocket/drawboard.xhtml b/webapps/examples/websocket/drawboard.xhtml
index 5fab402..4a4120e 100644
--- a/webapps/examples/websocket/drawboard.xhtml
+++ b/webapps/examples/websocket/drawboard.xhtml
@@ -875,7 +875,7 @@
         </p>
         <p>
             It uses asynchronous sending of messages so that it doesn't need separate threads
-            for each client to send messages.<br/>
+            for each client to send messages (this needs NIO or APR connector to be used).<br/>
             Each "Room" (where the drawing happens) uses a ReentrantLock to synchronize access
             (currently, only a single Room is implemented).
         </p>